Ren'Py is a programming language and runtime, intended to ease the creation of visual-novel type games. It contains features that make it easy to display thoughts, dialogue, and menus; to display images to the user; to write game logic; and to support the saving and loading of games. Ren'Py tries to be like an executable script, allowing you to get a working game without much more effort than is required to type the game script into the computer.
Ren'Py is implemented on top of python, and that python heritage shows through in many places. Many Ren'Py statements allow python expressions to be used, and there are also Ren'Py statements that allow for the execution of arbitrary python code. Many of the less-used features of Ren'Py are exposed to the user by way of python. By only requiring use of the simplest features of python, it's hoped that Ren'Py will be usable by all game authors.
The following is a simple but complete Ren'Py script. The colors are added to make it easier to read, and aren't part of the script proper.
init: image whitehouse = Image("whitehouse.jpg") image eileen happy = Image("eileen_happy.png") image eileen upset = Image("eileen_upset.png") label start: $ e = Character('Eileen') scene whitehouse show eileen happy e "I'm standing in front of the White House." show eileen upset e "I once wanted to go on a tour of the West Wing, but you have to know somebody to get in." "For some reason, she really seems upset about this." e "I considered sneaking in, but that probably isn't a good idea."
This example, shows many aspects of a Ren'Py script. The first four lines of the script serve to load in three images. After the label indicating the start of the game, a character is declared. The script then proceeds to display a picture of a character on top of a background image, and to have the character say two lines of dialogue, changing her picture in between. The POV character then thinks a line of dialogue, before the character says her final line.
We'll go into detail into what each of the statements here does over the course of this tutorial. For now, however let me just point out that the first 6 statements initialize the game, while the last 7 (starting with "scene") show images and display dialogue. As such, the bulk of a game is more like the last 7 then the first 6.
Of particular note is that a keyword isn't required to introduce dialogue. This allows visual novels consisting mostly of dialogue to be expressed in a concise form.
The largest division of a Ren'Py script is into files. By default, Ren'Py reads the script from all files ending in .rpy found in the game underneath the directory in which Ren'Py is installed. These script files may be read in any order, and all of them together make up a Ren'Py script.
Each of these files is divided into a series of logical lines. The first logical line of a file begins at the start of a file, and another logical line begins after each logical line ends, until the end of the file is reached. By default, a logical line is terminated by the first newline encountered. However, a line will not terminate if any of the following are true:
These rules should be the same as for Python.
Ren'Py also supports comments. A comment begins with a hash mark that is not contained within a string, and continues to, but does not include, the next newline character. Some examples are:
# This line contains only a comment. scene whitehouse # This line contains a statement as well.
If, after eliminating comments, a logical line is empty, that logical line is ignored.
Logical lines are then combined into blocks. Two logical lines are in the same block if the lines have the same indentation preceding them, and no logical line with a lesser amount of indentation occurs between the two lines. In the following example:
line 1 line a line b line 2 line c line d
There are three blocks. One block contains lines 1 and 2, another lines a and b, and the third contains lines c and d. This example can also serve to illustrate the concept of a block associated with a line. A block is associated with a line if the block starts on the next logical line following the line. For example, the block containing lines a and b is associated with line 1.
There are three kinds of blocks in an Ren'Py program. The most common is a block containing Ren'Py statements. Other blocks may contain menu entries or python code. The top-level block (the one that contains the first line of a file) is always a block of Ren'Py statements.
Before we can discuss statements, however, we must first discuss the tokens statements are built up out of. So here's a short list of all the tokens we use.
Keywords are words that appear in the source code. They're used to introduce a statement, or to delimit parts of a statement. You'll see keywords throughout the descriptions of statements. In grammar rules, keywords are in quotes. The keywords are:
at call hide if image init jump menu python return scene set show with while
A names consist of an alphabetic character or number, followed by zero or more alphabetic characters or underscores, so long as the string isn't a keyword.
An image_name is a list of one or more names, separated by a space.
A string begins with a " or a ', and continues until a matching unescaped " or ' is reached. Runs of whitespace inside a string are collapsed into a single space character, allowing strings to span multiple lines. The \ character is used inside the string to escape special characters, such as whitespace, quotes, and (as \n) to include a newline.
A simple_expression is a python expression that starts with a name, a string, or any python expression in parenthesis. This may be followed by any number of the following:
In general, simple expressions are strings, names, or method calls. They are not expected to contain operators.
A python_expression is an arbitrary python expression that may not include a colon. These expressions are generally used to express the conditions in the if and while statements.
We will be giving grammar rules for some of the statements. In these rules, a word in quotes means that that word is literally expected. Parenthesis are used to group things together, but they don't correspond to anything in the source code. Star, question mark, and plus are used to indicate that the token or group they are to the right of can occur zero or more, zero or one, or one or more times, respectively.
If we give a name for the rule, it will be separated from the body of the rule with a crude ascii-art arrow (->).
As the bulk of the content of a visual novel is presented to the user in the form of dialogue or thoughts, it's important that the ability to display text to the user be as convenient as possible. In Ren'Py, both actions are done through the say statement. The say statement doesn't require a keyword to introduce it. Instead, it consists of either a single string, or a simple_expression followed by a string.
We can distinguish two forms of the say statement, depending on if the simple_expression is provided. The single-argument form of say consists only of a single string. This form causes the string to be displayed to the user without any label as to who is saying it. Conventionally, this is used to indicate to the user thoughts or narration.
"I moved to my left, and she moved to her right." "So we were still blocking each other's path." "I then moved to my right, and at the same time she moved to her left." "We could be at this all day."
The two-argument form of the say statement first evaluates the expression to see what its value is. If the expression returns a string, that string is used as a character name to indicate who is saying the dialogue. If it returns an object, that object is responsible for displaying the dialogue to the user.
The most common type of object used in a dialogue statement is a Character object. Character objects have associated with them a name and a color. When a character object is asked to display a line of dialogue, it labels it with the character name in the character's signature color. In general, strings are used to indicate the names of lesser characters or ones who we have not discovered the name of yet, while character objects are used to indicate important characters.
"Girl" "Hi, my name is Eileen." e "Starting today, I'll be living here."
Finally, the string in a dialogue is subject to interpolation of variables. A string variable can be interpolated with %(name)s, while a number requires %(name)d. For example:
e "I know all about you." e "I know that you're %(player_age)d years old, and your zodiac sign is %(player_sign)s."
When the first object is a character object, that character object is given given complete control over how text is displated. As an example of this, the standard library includes a character object called "centered", which displays the text centered on the screen, without any background window.
centered "American Bishoujo presents..." centered "The Ren'Py Demo Game"
The say statement also takes a with clause that is used to control the transition that is used to introduce the dialogue or thought. Please see the section on transitions for details on how to use the with clause.
Menus present a user with a list of choices that can be made. In a visual novel game, menus are the primary means by which the user can influence the game's story.
A menu statement consists simply of the word menu, an optional name, and a colon. If the name is supplied it's treated as a label for this menu statement, as if the menu statement was preceded by a label statement. (See the section on control flow for details about the label statement.)
The menu statement must have a block associated with it. This block must contain one or more menuitems in it. There are four kinds of menuitems that can be contained in a menu block.
The first kind of menuitem is simply a string. This string is placed into a menu as a caption that cannot be selected. In general, captions are used to indicate what the menu is for, especially when it is not clear from the choices.
The second kind of menuitem gives a choice the user can make. Each choice must have a block of Ren'Py statements associated with the choice. If the choice is selected by the user, then block of statements associated with the choice is executed. A choice may also have an optional if clause that includes a python expression. This clause gives a condition that must be satisfied for the choice to be presented to the user. The terminating colon is what indicates that this menuitem is a choice.
The third kind of menuitem gives an expression that yields a set. There may only be one of this kind of menuitem per menu. If present, it's used to filter the list of choices shown to the user.
The final kind of menuitem is a with clause. This is used to specify the transition that introduces this menu. Please see the section on transitions for a discussion of this.
When a menu is to be shown to the user, the first thing that happens is that a list of captions and choices is built up from the menuitems associated with the menu. Each of the choices that has an expression associated with it has that expression evaluated, and if it evaluates to false, that choice is removed from the list. Finally, if a set is present, it is checked to see if the text of a choice is in the set. If the text is found, the choice is removed from the list.
If no choices survive this process, the menu is not displayed and execution continues with the next statement. Otherwise, the menu is presented to the user. When the user makes a choice, the text of that choice is added to the set (if one is present), and execution continues with the block associated with the choice. When that block finishes, execution continues with the statement after the menu.
Here's a fairly complicated menu that uses all three kinds of menuitems. Most menus in actual games will not be this complicated.
menu what_to_do: # Ensure that we can only do a given thing once. set what_to_do_set "What should we do today?" "Go to the movies.": "We went to the movies." "Go shopping.": "We went shopping, and the girls bought swimsuits." $ have_swimsuits = True "Go to the beach." if have_swimsuits: "We went to the beach together. I got to see the girls in their new swimsuits."
This menu will only allow a given activity to be chosen once, and will allow the user to chose to go to the beach only if the user has chosen to go shopping.
Without the ability to display images to the user, a visual novel would be a text adventure. Ren'Py controls image display by using a scene list, a list of things to be displayed to the user. Every time an interaction starts (that is, a line of dialogue or a menu is displayed), the things in the scene list are drawn to the screen, with the first being in the back and the last being in the front. A number of statements manipulate the scene list. Before we can explain them, however, we should first define a few terms.
An image_name is a space-separated list of names that's used to refer to an image. This list of names may not include keywords in it. The first element of the image name is known as the image tag, and is treated specially.
A Displayable is a python object implementing an interface that allows it to be displayed to the screen. A transform is a function that, when applied to a Displayable, returns a new Displayable. Transforms are used to change the way an image is displayed to the user. A transform_list is a comma-separated list of transforms. Display lists are applied from left to right.
An image_spec is an image name, an optional at list of transformers, and an optional with clause giving a transition. The image name is used to specify an image that will be shown. The at clause is a list of transformations that are applied to that image, which can control things like the placement of the image on the screen. The with clause is used to specify the transition that the image is being shown or hidden with. It will be discussed more in the section on transitions.
The first display statement is the image statement, which does binds an image name with a displayable defining that image. As the list of name bindings, is never saved, the image statement can only appear inside of an init block. The most popular python expression to use here is Image, which takes as an argument an image filename to load. Another popular choice is Animation, which is defined elsewhere in this document.
An example of image in use is:
init: image eileen happy = Image("eileen/happy.png") image eileen upset = Image("eileen/upset.png")
The next display statement is the show statement, which takes an image specifier and displays it on the screen. If an image with the same tag as the image given in the spec already exists on the scene list, it is replaced with the newly displayed Displayable. Otherwise, the new one is added to the end of the scene list (that is, closest to the user).
If an at list is present, the image is transformed with the at list before being added to the display list.
Automatically replacing an image with the same tag is a useful feature that allows characters to change expression without having to explicitly hide the old image.
The scene statement first clears the scene_list. If the optional image_spec is present, it is shown as if it was shown with the show statement. The best use for the image_spec on a scene command is show a background for the scene.
By default, no background is added to the screen. (See config.background to change this.) Without such a background, Ren'Py will produce odd results if there is not at least one image in the scene list that is the full size of the screen. So we strongly advise that the scene statement always be used with an image, and that image be one that takes up the full width and height of the screen.
We can put together the scene and show statements to get the following example:
scene living_room show eileen happy at left e "I'm feeling happy right now." show eileen upset at left e "But sometimes, I can get upset for no good reason."
The hide statement is used to remove an image from the scene list. The image spec is parsed for a tag, and any image matching that tag is remove from the scene. The at list is ignored (but should be valid or omitted), and transitions associated with the image spec are run.
Hide is a rarely used display statement. The show statement automatically replaces an old image when a character changes emotion, and the scene image removes all images when the scene changes. Hide is generally only used for when a character leaves in the middle of a scene.
e "Well, I'll be going now." hide eileen "And with that, she left."
These four statements, along with the library of Displayables and transforms provided with Ren'Py, should be enough to render most scenes needed in a visual novel type game.
This section includes functions that are useful when defining images or composing scenes.
Image | (filename): |
Returns a Displayable that is an image that is loaded from a file on disk.
filename - The filename that the image is loaded from. Many common file formats are supported.
The Image function is generally used in conjunction with the image statement. For example:
init: image eileen happy = Image("9a_happy.png") image eileen vhappy = Image("9a_vhappy.png") image eileen concerned = Image("9a_concerned.png")
Solid | (color): |
Returns a Displayable that is solid, and filled with a single color. A Solid expands to fill all the space allocated to it, making it suitable for use as a background.
color - An RGBA tuple, giving the color that the display will be filled with.
Frame | (filename, xborder, yborder): |
Returns a Displayable that is a frame, based on the supplied image filename. A frame is an image that is automatically rescaled to the size allocated to it. The image has borders that are only scaled in one axis. The region within xborder pixels of the left and right borders is only scaled in the y direction, while the region within yborder pixels of the top and bottom axis is scaled only in the x direction. The corners are not scaled at all, while the center of the image is scaled in both x and y directions.
filename - The file that the original image will be read from.
xborder - The number of pixels in the x direction to use as a border.
yborder - The number of pixels in the y direction to use as a border.
For better performance, have the image file share a dimension length in common with the size the frame will be rendered at. We detect this and avoid scaling if possible.
init: style.window.background = Frame("frame.png", 125, 25)
Animation | (*args): |
A Displayable that draws an animation, which is a series of images that are displayed with time delays between them.
Odd (first, third, fifth, etc.) arguments to Animation are interpreted as image filenames, while even arguments are the time to delay between each image. If the number of arguments is odd, the animation will stop with the last image (well, actually delay for a year before looping). Otherwise, the animation will restart after the final delay time.
init: image animation = Animation("frame_1.png", 0.25, "frame_2.png", 0.25, "frame_3.png", 0.25)
Frames are normally used in conjunction with styles, to provide the displayable that is the background for a window containing a menu or dialogue.
Position | (**properties): |
Position, when given position properties as arguments, returns a callable that can be passed to the "at" clause of a show or scene statement to display the image at the given location. See the section below on position properties to get a full explanation of how they are used to lay things out, but hopefully this example will show how Position can be used:
init: left = Position(xpos=0.0, xanchor='left') center = Position(xpos=0.5, xanchor='center') right = Position(xpos=1.0, xanchor='right') top = Position(xpos=0.5, xanchor='center', ypos=0.0, yanchor='top') show eileen happy at left
Pan | (startpos, endpos, time): |
Pan, when given the appropriate arguments, gives an object that can be passed to the at clause of that image to cause the image to be panned on the screen. The parameters startpos and endpos are tuples, containing the x and y coordinates of the upper-left hand corner of the screen relative to the image. Time is the time it will take this position to move from startpos to endpos.
As the current implementation of Ren'Py is quite limited, there are quite a few restrictions that we put on pan. The big one is that there always must be a screen's worth of pixels to the right and below the start and end positions. Failure to ensure this may lead to inconsistent rendering.
Hopefully, an example will demonstrate how Pan is used. For this example, assume that the screen is 800 x 600, and that the image marspan is 2400 x 600 pixels in size. We want to take 10 seconds to pan from left to right on the image.
scene marspan at Pan((0, 0), (1600, 0), 10.0)
Please note that the pan will be immediately displayed, and that Ren'Py will not wait for it to complete before moving on to the next statement. This may lead to the pan being overlayed with text or dialogue. You may want to use a call to renpy.pause to delay for the time it will take to complete the pan.
Finally, also note that when a pan is completed, the image locks into the ending position.
By default, Ren'Py displays each scene list by replacing the old scene list with a new one. This is appropriate in general (such as for emotion changes), but it may be boring for large scene changes, such as a change in scene or a character entering or leaving the scene. Ren'Py supports transitions that control how changes to the scene lists are exposed to the user.
Transitions occur between the last scene list that was shown to the user, and the current scene list that has been updated using the scene, show, or hide statements. A transition takes both lists as input, and is responsible for displaying the transition between them to the user. Each transition runs for a given amount of time, but may be dismissed early by the user. Once a transition is shown, the scene list is considered shown for the purposes of future transitions.
Transitions are introduced with the with statement. The with statement takes an expression that is suitable for use with the with statement (that is, a callable that takes as input two scene lists), and runs that transition. Alternatively, if the expression is None, then the with statement has the effect of showing the scene to the user, and returning instantly. This is useful in conjunction with a future with statement, so that only some changes to the scene list will be transitioned in.
An example is in order. First, let us define a few objects that can be passed in as the argument to a with statement:
init: # Fades to black, then to the new scene. fade = Fade(0.5, 0, 0.5) # Dissolves between old and new scenes. dissolve = Dissolve(0.5)
A simple use of with would be to place it after a series of show and hide statements of the program. As an example:
scene whitehouse show eileen happy with fade
This series of statements will cause the old scene (displayed before these statements started) to fade to black, and be replaced with the new scene all at once. This is a useful behavior, for example, when we are replacing a scene with a new one, such as when the story changes locations.
scene whitehouse with None show eileen happy with dissolve
The "with None" statement is useful to break changes to the scene list into parts, as in the example above. When run, the background will be instantly shown, and then the character image will be dissolved in over the background.
Another use of the "with None" statement is to remove transient elements before a transition begins. By default, the scene list includes transient elements like dialogue, thoughts, and menus. "with None" always executes without these elements, and so gets rid of them.
The "show", "hide", and "scene" statements all take a with clause. One of these statement with a with clause associated with it is actually converted into three statements: A "with None" statement, the original statement sans the with clause, and the with clause as a with statement. For example:
scene whitehouse with fade show eileen happy at left with dissolve show lucy happy at right with dissolve
becomes
with None scene whitehouse with fade with None show eileen happy at left with dissolve with None show lucy happy at right with dissolve
This has the effect of fading out the old scene and fading in the new background, then dissolving in the characters one after the other.
We also allow with clauses to be supplied for say and menu statements. When a with clause is supplied on one of these statements, the transition is used to introduce the say or menu element. For example,
e "How are you doing?" with dissolve
Will dissolve in a line of dialogue. The line of dialogue will be dismissed immediately, unless it is followed by a with statement or clause that causes it to transition to something else.
There is one variable that controls transitions:
Functions that return things that are useful as arguments to with statements or clauses are:
Fade | (in_time, hold_time, out_time, color=(0, 0, 0)): |
This returns an object that can be used as an argument to a with statement to fade the old scene into a solid color, waits for a given amount of time, and then fades from the solid color into the new scene.
in_time - The amount of time that will be spent fading from the old scene to the solid color. A float, given as seconds.
hold_time - The amount of time that will be spent displaying the solid color. A float, given as seconds.
out_time - The amount of time that will be spent fading from the solid color to the new scene. A float, given as seconds.
color - The solid color that will be faded to. This is an RGB triple, where each element is in the range 0 to 255. This defaults to black.
Dissolve | (delay): |
This dissolves from the old scene to the new scene, by overlaying the new scene on top of the old scene and varying its alpha from 0 to 255.
delay - The amount of time the dissolve will take.
Control statements change the order in which statements in a Ren'Py script execute. These statements allow for control transfers, conditional execution, and procedure calls.
The label statements assigns a name to a point in the program, allowing control to be transfered to this point by the jump or call statements. The label statement may have a block associated with it. If it does, the statement executed after the label is the first statement in the block. Otherwise, the next statement to be executed is the first statement after the label.
The jump statement unconditionally transfers control to the statement with the given name. If the name does not exist, an error is raised.
label loop_start: e "Oh no! It looks like we're trapped in an infinite loop." jump loop_start
The call statement transfers control to the location given. It also pushes the name of the return site onto the return stack, allowing the return statement to return to the statement after the call site.
If the optional from clause is present, it has the effect of including a label statement with the given name as the statement immediately following the call statement. An explicit label is required here to ensure that saved games with return stacks can return to the proper place when loaded on a changed script. On the other hand, from clauses may be distracting when a game is still under development. A script will be provided to add from clauses to call statements right before a game is released.
If the return stack is not empty, the return statement pops the top return site off of it and transfers control there. Otherwise, it terminates execution without raising an error.
e "First, we will call a subroutine." call subroutine from _call_site_1 e "Finally, we will exit the program." return label subroutine: e "Next, we will return from the subroutine." return
The if statement is used to conditionally execute a block of statements. It is the only statement that consists of more than one logical line in the same block. The initial if statement may be followed by zero or more elif clauses, concluded with an optional else clause. The expression is evaluated for each clause in turn, and if it evaluates to a true value, then the block associated with that clause is executed. If no expression evaluates to true, then the block associated with the else clause is executed. (If else clause exists, execution immediately continues with the next statement.) In any case, at the end of the block, control is transferred to the statement following the if statement.
if points >= 10: e "Congratulations! You're getting the best ending!" elif points >= 5: e "It's the good ending for you." else: e "Sorry, you're about to get the bad ending."
The while statement executes its block while the expression is true. Specifically, each time the while statement executes, it evaluates the expression. If the expression is true, control is transferred to the first statement of the block associated with the while loop. If it is false, then control is transferred to the next statement. The while statement is the statement that normally executes after the last statement in the block, causing the condition to be evaluated again and the loop to repeat.
This definition of a while loop means that it would be hard to implement statements like python's "continue" or "break". These statements can be easily faked with labels in the right places and jumps to those labels. This definition also means that it's possible to jump into the middle of the block associated with a while loop and, if at the end of the block the condition is true, have the while loop repeat the block.
while not endgame: "It's now morning. Time to get up and seize the day." call morning call afternoon call evening "Well, time to call it a night." "Now it's time to wake up and face the endgame."
The pass statement can be used where a block is required, but there's no statement that can be placed in that block. When executed, pass has no effect.
For example, pass can be used in a menu if we don't want to take any action when a choice is selected.
menu: "Should I go to the movies?" "Yes": call go_see_movie "No": pass "Now it's getting close to dinner time, and I'm starving."
The init statement is used to introduce a block of code that should be run when the game first starts. When the game is first loaded, the script is scanned for init blocks, and code in init blocks is run in an arbitrary order. An init statement encountered during execution, however, is treated as a pass statement, and the block is not executed.
There are two Ren'Py statements that allow python statements to be mixed with Ren'Py code. Any statement beginning with a dollar-sign ('$') will be interpreted as python code extending to the end of the logical line. This form can only include a python statement containing a single logical line. (So python control constructs cannot be used.)
The other way to introduce python statements is with a python block statement. The block associated with this statement, along with any block inside those blocks, is interpreted as python code that is passed to the python interpreter. The block nesting structure is reflected in the python code that is interpreted, so that python control structures will work as advertised.
If the optional hide keyword is added to a python block statements, local variables created in the block will not be added to the store. The variables in the store can be accessed as attributes of the store, however.
# Toggle fullscreen mode. $ config.fullscreen = not config.fullscreen # Pointless python that uses a loop. python: for i in ('e', 'l'): globals()[i].points = 0
In general, if a Ren'Py construct exists that does what you want (like while or if), it should be used in preference to a python block, unless a large amount of code is to be executed with no user interaction.
When Ren'Py is first invoked, it first tries to parse all the .rpy files in the game directory. If at least one .rpy file exists, it is loaded, and the script is then written out in a serialized form. If no .rpy files exist, but the serialized script does exist, the serialized script is read back in from disk.
Once the script is loaded, the first thing that occurs is that it is scanned for init blocks. These init blocks are then run immediately, in no particular order. The init blocks should do things like loading images and changing Ren'Py configuration. On no account should an init block try to display an image or interact with the user, as the display system is not yet initialized, and so such interaction will not work.
After the last init block has finished running, the display is initialized, and the actual game can begin. It's expected that each game will have a label named "start". The game is begun by jumping to this start label. Execution proceeds from there, terminating when the end of a file or a return statement is reached.
In Ren'Py, saving, loading, and rollback are three actions that share quite a bit of infrastructure. So, we'll discuss them together in this section. We will pay special attention as to what it takes to support loading of a saved game when the script changes between saving in loading, while running on the same Ren'Py version. Apart from designated bug-fix releases of Ren'Py, we do not plan to support loading a save created on an older version of Ren'Py in a newer release of Ren'Py. Once a game has been released once, it's expected that future releases of that game will use the same release of Ren'Py.
The state of the game in Ren'Py consists of two basic parts. The interpreter state consists of state that the user never manipulates directly, while the user state consists of variables that have been changed by the user, and objects reachable from those variables. Currently, the interpreter state that is saved consists of:
These items are automatically saved, restored, or rolled-back when the appropriate commands are invoked by the user. You do not need do anything for this state to be handled, and there is no way to prevent these things from being saved.
There are some important portions of interpreter state that are not saved, and therefore should only be changed in init: blocks. These are:
To deal with this, one should ensure that all images and config variables are set up in init blocks, and then left alone for the life of the game. In addition, as a scene list in a saved game may contain references to image names, once an image name is defined in a released version of the game, the image name should remain defined in all future release of the game.
The other kind of state that Ren'Py can save and restore is user state. User state consists of the values of all variables that are changed after the end of the init phase, as well as all data reachable from those variables.
It's important to clarify what it means for a variable to be changed. In Python and Ren'Py, variable names are bound to objects. The variable changes when a new object is assigned to it. It does not change when the object that is assigned to it changes.
As an example, in the following code:
init: $ a = 0 $ state = object() $ a = 1 $ b = [ ] $ state.love_love_points = a + 1
The variables a and l are updated after the end of the init: block, while state is not updated. The variable a is assigned a different integer object, while b is assigned a new empty list. While a field in the Character object is changed by the last statement, state still points to the same object, and therefore it is not considered to have changed. (And therefore, the object isn't considered to be part of user state.)
User state is gathered by first finding all variables changed since the end of the init phase. We then find all objects reachable from one of those variables through some combination of field access, iteration, or iteration over items (as in a dictionary). This combination of variable bindings and object values comprises the user state.
It's important that everything that's kept in the user state can be pickled (serialized). Thankfully, most python constructs can be, including booleans, integers, floating-point numbers, strings, lists, tuples, dictionaries, and most objects. You can also refer to your own classes and functions, provided that they are defined in a python block (not a python hide block) inside an init block, and always exist with the same name in later versions of the script. Things that can't be pickled include strange objects like iterators and files. Usually, you don't have to worry much about this.
As the game is played, Ren'Py logs all changes to user and interpreter state. When the game is saved, it writes this log out to disk, alongside the current state.
When the game is loaded back in, the variables are reset to what the were when the init code in the current version of the script finished running. The saved user state is then merged with this, with saved user state overriding any variable that was also assigned in the init code. Finally, a rollback is triggered.
The rollback that is triggered on load ends when it can find a statement that has the same name as it had when it was encountered in the log. When the script hasn't changed, all statement have the same name, so the effect of the rollback is to bring control back to the start of the statement that was executing when the user saved. When the script has changed, however, the only statements that retain their names are statements that have an explicit name specified. (Such as a label, a menu with a name, or a call with a from clause.) The game will rollback to the start of one of these statements. To ensure that rollback works correctly when the script changes, a label that exists in a released version of the game should continue to exist in all future version of the game.
When a rollback occurs, both user and interpreter state are restored to what they were when the statement that is being rolled-back to began executing. The statement is then executed again, and play continues normally.
Please note that we can only roll back the currently-executing statement, and not the return sites listed on the return stack. If the name of a return site changes, we will not be able to return from a procedure call, and the script will crash. If a return site has an explicit name, however, that name is returned to even if the script change. Because of this, it's important that every call site in a released game have a from clause associated with it.
Finally, if allowed, rollback can be invoked explicitly by user input. When such a rollback occurs, we first look for a previous statement that is a checkpoint (checkpoints are say and menu statements, as well as python blocks that called renpy.checkpoint()). Once a checkpoint is found, we look for a statement which has a name that exists in the current script (this is normally the same statement). We then rollback to that statement and begin executing again.
The upshot of this is that when a user rollback occurs, the game is reverted to the start of the say or menu statement that executed before the currently executing one.
There is one variable that controls the behavior of loading and saving:
While these rules may seem to be complex, it's hoped that in practice they can be reduced to a simple heuristic: Any variable changed outside of an init block, and any object reachable from such a variable, will be saved, loaded, and rolled-back properly.
Persistent data is data that is saved that is not associated with a single game. One possible use of it is to store information about things that have been unlocked, such as an image gallery that is only unlocked when an ending has been reached.
Persistent data is stored as fields on the "persistent" object. This object is special, as uninitialized fields are forced to take the value None. A change to this object is visible in every game that the player undertakes.
Take as an example an unlockable image gallery. The code to display the gallery look like:
label gallery: if not persistent.gallery_unlocked: show background centered "You haven't unlocked this gallery yet." $ renpy.full_restart() # Actually show the gallery here.
Then, to unlock the gallery, run the following code somewhere in your program.
$ persistent.gallery_unlocked = True
The following functions either implement new game behavior that didn't merit its own statement, or complement the behavior of statements.
init: e = Character("Eileen", color=(200, 255, 200, 255)) e "My name is shown in full, and in green."
It's probably best to define Character objects in an init block, and not attempt to set attributes on them.
Character | (name, who_style='say_label', what_style='say_dialogue', window_style='say_window', **properties): |
The character object contains information about a character. When passed as the first argument to a say statement, it can control the name that is displayed to the user, and the style of the label showing the name, the text of the dialogue, and the window containing both the label and the dialogue.
name - The name of the character, as shown to the user.
who_style - The name of the style that is applied to the characters name when it is shown to the user.
what_style - The name of the style that is applied to the body of the character's dialogue, when it is shown to the user.
window_style - The name of the style of the window containing all the dialogue.
properties - Additional style properties, that are applied to the label containing the character's name.
renpy.pause | (delay=None): |
When called, this pauses and waits for the user to click before advancing the script.
renpy.input | (prompt, default='', length=None): |
This pops up a window requesting that the user enter in some text. It returns the entered text.
prompt - A prompt that is used to ask the user for the text.
default - A default for the text that this input can return.
length - If given, a limit to the amount of text that this function will return.
$ name = renpy.input("What is your name?", "Joe User", length=20) e "Pleased to meet you, %(name)s."
renpy.full_restart | (): |
This causes a full restart of Ren'Py. This clears the store and configuration, and re-runs init before branching off to start. It's very close to what happens when we quit out and re-run the interpreter, save for some caches not being cleared.
renpy.quit | (): |
This causes Ren'Py to exit entirely.
renpy.watch | (expression): |
This watches the given python expression, by displaying it in the upper-right corner of the screen. The expression should always be defined, never throwing an exception.
This will replace any overlay defined by the program.
renpy.windows | (): |
Returns true if we're running on Windows. This is generally used as a test when setting styles.
Ren'Py supports playing music in the background of your game. Theoretically, Ren'Py should support any format SDL_mixer supports (mp3, ogg, midi, mod, and more), but we've only tested mp3 and ogg support. The music that is currently playing is kept as part of the interpreter state, and is restored when a player loads a game or rolls back the game state, automatically.
Music must exist in real files (not archive files), named with paths starting at the game directory. The filename that the music is stored in is saved with the game state. If the filename changes between game releases, the game will still proceed, but music that was playing when the game was saved may be absent when the game is loaded.
The playing of music is controlled by a pair of Python function calls:
renpy.music_start | (filename, loops=-1, startpos=0.0): |
This starts music playing. If a music track is already playing, stops that track in favor of this one.
filename - The file that the music will be played from. This is relative to the game directory, and must be a real file (so it cannot be stored in an archive.)
loops - The number of times the music will loop after it finishes playing. If negative, the music will loop indefinitely. Please note that even once the song has finished, rollback or load may cause it to start playing again. So it may not be safe to have this set to a non-negative value.
startpos - The number of seconds into the music to start playing.
renpy.music_stop | (): |
Stops the currently playing music track.
Here's an example of a script fragment that comes with musical accompaniment.
e "Lemma kept asking for support for playing music." # Loads game/music/taps.mp3 $ renpy.music_start("music/taps.mp3") show eileen flaming mad e "Well, we'll show him." $ renpy.music_stop() show eileen wink e "Just kidding."
Much of the the configuration of Ren'Py is done using configuration variable. These variable, when assigned in a python block, change the behavior of the interpreter. As configuration variables aren't saved, and many need to be set before the GUI initializes, it makes sense to set all configuration variable inside init blocks. An example setting of variables is:
init: $ config.screen_width = 640 $ config.screen_width = 480
Ren'Py includes a style system that allows the user to control the styles of text and windows (including things that inherit from window, like buttons). A style inheritance mechanism allows a single change to (for example) font size to affect the entire game, while at the same time allowing fine-grained control when it is desired.
There are two families of style properties. Text properties are used to control the font, size, and color of text, while window properties are used to control the layout, background, and padding of windows. If a widget displays text inside a window (as a renpy.TextButton does), then it respects both text and window properties. A widget is a Displayable that can accept properties.
Many style properties take as an argument RGBA tuples. These are python tuples containing four values, representing the red, green, blue, and alpha components of a color. Each of these values is a number between 0 and 255. Alpha is used to control how opaque the thing should be, with 0 being transparent, and 255 being fully opaque. It's probably easiest to show an example of RGBA tuples being used as part of styles.
init: # Selected menu choices should be yellow and solid. $ style.menu_choice_selected.color = (255, 255, 0, 255) # Unselected menu choices should be cyan and translucent. $ style.menu_choice_unselected = (0, 255, 255, 128)
The following are properties that can be applied to Displayables that take text styles.
font --- A filename that contains a truetype font that is used to display the text on the screen. Example: "Vera.ttf"
size --- The size of the font that is used to display the text on the screen. Please note that the meaning of this can vary from font to font, and bears only a weak relationship with the number of pixels high that the font will be on the screen. Example: 22
color --- The color in which the text will be displayed on the screen, as an RGBA tuple. Example: (255, 255, 255, 255)
drop_shadow --- This is used to control the generation of a drop shadow on text. It's either a 2-element tuple, or None. If it's a tuple, then the 2 elements control the drop shadow offsets in X and Y, respectively. (Both numbers should be positive for best results.) If None, then no drop shadow is created for the text. Example: (2, 3)
drop_shadow_color --- An RGBA tuple that's used to give the color of the drop shadow. Example: (0, 0, 0, 128)
minwidth --- The minimum width in pixels of this text. If the rendered text is smaller than this, it is right-padded with whitespace until it is at least this many pixels long.
line_height_fudge --- On some platforms (Windows), the height of a line of text isn't reported correctly. This can lead to an overly-large spacing between lines. This is a number that is added to the reported line spacing. If negative, the lines will be pushed closer together. This is very platform-dependent, so you may want to condition it on the result of renpy.windows().
textalign --- This is used to control the horizontal alignment of the lines of text in the area allocated to the Text widget containing that text. It only really has any effect if the text is more than one line long. It's a number between 0 and 1, which gives the fraction of empty space that should be to the left of each line of text. (To center text, it should be 0.5.)
Window properties are used to control the display of windows, and other widgets that involve a rectangular area with a background being displayed on the screen.
background --- A Displayable that is used as the background for the window. This needs to be a Displayable that always draws exactly the size requested of it, which usually means either a Solid or a renpy.Frame. This can also be None, which means that there is no background on this window. (All the other window properties that refer to a background still work. Just think of them as if their background was transparent.) Example: Solid((0, 0, 128, 128))
xmargin --- The amount of transparent space that is left around this window in the x direction, in pixels.
xpadding --- The amount of space left between the edge of the background of the window, and the edge of the content contained within this window, in the x direction, in pixels.
xfill --- If True, the window will expand to fill all available space in the x direction. If False, it will shrink to fit its contents.
xminimum --- The minimum size of this window in the x direction, including margins and padding. If the window would be smaller than this, it is grown to be at least this size.
ymargin --- The amount of transparent space that is left around this window in the y direction, in pixels.
ypadding --- The amount of space left between the edge of the background of the window, and the edge of the content contained within this window, in the y direction, in pixels.
yfill --- If True, the window will expand to fill all available space in the y direction. If False, it will shrink to fit its contents.
yminimum --- The minimum size of this window in the y direction, including margins and padding. If the window would be smaller than this, it is grown to be at least this size.
Position properties are applied to widgets that are smaller than the space allocated to them. They control the placement of the widget in the space. For example, position properties can be used to control the placement of a dialogue window on the screen.
Position properties work best when a small widget is placed into empty space. This is the case for say windows, and the menus that are displayed as part of the main menu. Position properties work on many updates, but the vagaries of how space is allocated by widgets may make some of the results counterintuitive.
Positioning is done by specifying the placement of an anchor that is part of a widget within the space allocated to that widget. The position is either specified as an absolute number of pixels or a fraction of the available space, while the position of the anchor within the widget is done by selecting one of three settings for each dimension.
xpos --- Controls the positioning of the widget anchor in the x dimension. If an integer, this is taken as the number of pixels to the left of the anchor. If a float, this is taken as a fraction of the available space in the x direction.
xanchor --- Controls the placement of the anchor within the widget, in the x dimension. This can be one of 'left', 'center', or 'right'.
ypos --- Controls the positioning of the widget anchor in the y dimension. If an integer, this is taken as the number of pixels to the left of the anchor. If a float, this is taken as a fraction of the available space in the y direction.
yanchor --- Controls the placement of the anchor within the widget, in the y dimension. This can be one of 'top', 'center', or 'bottom'.
In Ren'Py, buttons and their labels support the idea of hovering: using different sets of properties when the mouse is over or not over the widget. If the mouse is over the widget, then that widget is hovered, else it is idle. On these widgets, Ren'Py will look up a property prefixed with "hover_" or "idle_" (as appropriate) in a style, before looking up an unprefixed property. If neither form is found on a style, the process is repeated again on the parent style. (The search pattern for background on a hovered button goes: button.hover_background, button.background, default.hover_background, default.background.)
As an example, take assigning a background to buttons that changes when the button is hovered over.
init:
style.button.hover_background = Solid((255, 255, 255, 255))
style.button.idle_background = Solid(0, 0, 0, 255))
For properties to be useful, there needs to be a way to change the properties associated with a given widget. Ren'Py provides several ways to do this.
The most common way to do this is to change the definition of one of the standard styles. Widgets that use this style, or one of the styles that inherits from it, will pick up the change and change how they are displayed.
The following example code ensures that the label of a button is colored light green and aligned to the left. It also changes the background images that are used when the button is idled and hovered.
init: $ style.button.color = (128, 255, 128, 255) $ style.button.xpos = 0 $ style.button_idled.background = \ renpy.Frame(renpy.Image("button_idled.png"), xborder=10, yborder=10) $ style.button_hover.background = \ renpy.Frame(renpy.Image("button_hover.png"), xborder=10, yborder=10)
This is the only way to change the look of widgets that are automatically generated, such as those used to implement the interface of the say and menu statements, except for say statements that are routed through an object, like a character object.
The second way to change the look of a widget is to change the name of the style associated with that widgets. All widgets have one or more parameters that take styles that the widget take on. One can define a new style using style.new_style, and modify that style, and tell widgets to use that style when appropriate.
While normally new styles can only be assigned to widgets constructed by the user, there are some exceptions to this rule. The Character object takes as optional arguments styles that are given to the say window, character name, and dialogue text.
The final way of assigning properties is to list them as keyword arguments on a constructor that takes properties. (Indicated by having **properties in its argument list.) Properties that are given in this way take precedence over all other properties. The Character argument also takes properties that are applied to the window and the label.
In this section find a list of the standard styles that come with Ren'Py, either built-in or in the standard library. Before each in parentheses, we list the types of properties that are used on the style, and "hover" if the default use of the style supports hovering.
For some reason, many people seem to want to distribute their games in an obfuscated form, making it difficult to read the script, or to view the images associated with a game. Ren'Py supports this, to a point, by compiling scripts and allowing images to be archived. While a moderately skilled programmer could easily crack this obfuscation, it should be enough to protect a game from casual browsing.
Obfuscating the script is practically automatic. Every time the game runs, any .rpy file in the game directory is written out as an .rpyc file. These .rpyc files are enough to run the game, so simply running the game once (to create the .rpyc files) and then deleting (or more appropriately, moving away) the .rpy files will leave you with a runnable game with an obfuscated script. As a bonus, the .rpyc files are already parsed, improving game load time. (If a directory contains .rpy and .rpyc files with the same stem, the newer one of them is chosen, meaning all games get this performance improvement.)
Images can be archived using the archive.py or archive.exe programs that ship as part of Ren'Py. To use this, one can change into the game directory and run a command like:
../archive.exe images *.png *.jpg
This will create the files images.rpa and images.rpi in the game directory. One can then add to the script a line like:
init: $ config.archives = [ 'images' ]
Then, delete the .jpg and .png files from the game directory. Ren'Py will look in the images archive to find images that need to be loaded.
While Ren'Py is by default set up to operate in an English speaking environment, it is not limited to such settings. Assuming a proper font is loaded, Ren'Py scripts can contain any language expressible in Unicode.
There are two things in the Ren'Py library that may need to be translated into a user's language. The first is the main menu. There is no explicit support for doing this, but as the library.main_menu variable supports changing the text of the main menu, it also supports translating said text.
The second thing that needs to be translated is the game menu. The library.translations dictionary is used to translate text in the game menu into your language. If a key in this map corresponds to the English text that would be displayed, the value corresponding to that key is displayed again. For example:
init: $ library.translations = { "Yes" : u"HIja'", "No" : u"ghobe'", # etc. }
The u characters prefixed to the strings on the right, while not strictly necessary in this case, are used to tell Python that the string is in Unicode rather than ASCII. This is useful if your language uses non-ascii characters.