Organizing a Ren’Py Project

Organizing a Ren’Py Project

So your project is getting pretty big now, and you want to organize it better. Or perhaps someone directed you here after you asked a question like “where do I put that code in my Ren’Py game?”. There are a few key facts to know about how Ren’Py projects work, and how you can stay organized while working on your game.

You can make as many .rpy files as you like

You are not restricted to using the four files that come with every default Ren’Py project (options.rpygui.rpyscreens.rpy, and script.rpy). You can rename those files if you like, relocate code in them, add to them, delete them, whatever you like (though, each of the four default files has some essential information in it, so you’ll either need to retool your game to not need those files or make sure you don’t delete the essentials).

All you need to do to create a new script file for your game is make a new file in the game/ folder and give it the .rpy extension. Then you can start writing code in it! There’s nothing fancy you need to do to get code from a new file to work in your game, and very few rules about what you can name the files and what you can put in them. 

.rpy files can be called just about anything you like, though I suggest you stick to letters and numbers. Spaces and underscores are all right, too. Ren’Py also reserves the use of files that begin with 00, so if you need a file to run early, try to use something like 01_myfile.rpy instead (see the section below for more).

You can also create folders inside your game/ folder to house as many .rpy files as you like. For example, you might organize your game folder as follows:

My Game Project/
├─ game/
│  ├─ Ashwin Route/
│  │  ├─ bad_end.rpy
│  │  ├─ good_end.rpy
│  │  ├─ normal_end.rpy
│  ├─ Xia Route/
│  │  ├─ bad_end.rpy
│  │  ├─ good_end.rpy
│  │  ├─ normal_end.rpy
│  ├─ Zoran Route/
│  │  ├─ bad_end.rpy
│  │  ├─ good_end.rpy
│  │  ├─ normal_end.rpy
│  ├─ common_route.rpy
│  ├─ gui.rpy
│  ├─ options.rpy
│  ├─ screens.rpy

Note that there are three folders inside the game/ folder – one for Ashwin, Xia, and Zoran’s routes. Each route has a bad, good, and normal end file. Note that it’s fine there are three good_end.rpy files, because each one is in a different folder.

Other common organization methods include:

  • Creating a file where you declare all your variables (e.g. with default. See: Setting up variables)
    • Similarly, creating files to hold things like image definitions, ATL, or functions
    • This isn’t bad if you don’t have too many variables/images/functions etc. If you’ve got a lot, you might consider breaking them up further into categories which each get their own .rpy file, or grouping definitions together in files where they’re used.
  • Organizing DLC (downloadable content) into separate sub-folders
  • Creating a “Screens” folder which has a file for each UI screen
  • Creating a .rpy file for each chapter or scene in your game
  • Putting configuration values (e.g. define config.some_configuration_value = True) in options.rpy (as that’s where the default project puts most configuration values).

Which method is best will depend on your own tastes, the size and gameplay of your project, and various other factors. The general rules I like to follow are:

  • If a file is over 1000 lines long, consider splitting it up into multiple files
  • If a bunch of files are related, they should go in their own folder
  • If you don’t know which folder to look in to find a particular file, you need to rethink your folder structure
    • Typically I try to relocate the file to the first place I thought to look for it
    • Similarly, if I open a file and the code I need is not there, I tend to relocate it to the file where I thought to look for it.

For some projects, especially larger ones, the following tips may also be helpful:

  • If you’re using layered images, make a separate file for all those declarations (and maybe even separate files for an individual character’s layered images, if they’re long)
  • Make a new .rpy file for any big systems (e.g. galleries, combat, minigames)
  • Keep any closely associated styles, systems, screens etc. in the same file where possible
    • For example, if I’m creating a character selection screen, I’ll try to keep all my image declarations, variables, styles, etc in the same file with the screen itself
    • I tend to relocate most screen declarations + their styles to their own folders to keep them easy to find later

Notes on rpyc files

You may have noticed rpyc files with the same names as your rpy files inside your game folder. So, next to script.rpy, you’ll see there’s also a script.rpyc file. If you try to open it, it generally looks like a whole bunch of gibberish.

An rpyc file is a Compiled Ren’Py script file. Basically, rpy files are there for you to read and edit the code of your game. An rpyc file is there for the computer to know how to read and run your game. You can actually run your game off of the rpyc files alone, as Ren’Py only uses the rpy files in order to compile (put together) the rpyc files.

Knowing this, you can understand one other crucial aspect of organizing your script files – if you rename a script (rpy) file, you must either a) ALSO rename its corresponding rpyc file, or b) remove the rpyc file. Similarly, if you relocate a script file to a different folder, you must either a) ALSO take its corresponding rpyc file with it, or b) remove the rpyc file.

In general, it’s best to move and/or rename the rpyc file alongside the rpy file. Otherwise, you will get a variety of errors, including things like The label <labelname> is defined twice or Exception: <variablename> is being given a default a second time. This is because relocating or renaming the file prompts Ren’Py to create an accompanying rpyc for it, but the old rpyc still exists in your project, so Ren’Py reads the information off of it as well. Both rpyc files have the same information since they were made from the same rpy file, so Ren’Py thinks there are two default statements for a particular variable, two label start statements, etc.

Deleting an rpyc file to force Ren’Py to regenerate a new one is all right so long as you have not released any previous versions of your game AND you are not trying to keep any of your previous saves backwards-compatible. Ren’Py puts very important information into rpyc files so that it knows where to load save games. Otherwise, you must always rename and/or relocate the rpyc files with their corresponding rpy files rather than deleting them.

For a more in-depth look at what rpyc files are for and what they do, take a look at this article by Ren’Py Tom, the creator of the Ren’Py engine: https://www.patreon.com/posts/under-hood-rpyc-23035810 Note: the article is publicly available and linked through Patreon.

All .rpy files can read code from other .rpy files

You don’t have to worry about header files or importing variables, labels, images etc. – all .rpy files are compiled into what acts as one giant .rpy file. So, if you have default xia_affection = 0 in a file called variables.rpy, you can freely use the variable xia_affection in a file such as xia_route.rpy.

That said, there are some things to take note of. For most Ren’Py games, order of initialization won’t really matter. Unless you’re running into errors where Ren’Py complains something isn’t declared before you use it, you shouldn’t worry about the below information. In the case that you need to worry about initialization order, however, the following information will be useful.

  • define always happens before default
  • You can use init offsets (e.g. init -99:init 100 python:, or init offset = -2) to adjust the order of initialization. Negative/smaller numbers happen earlier than positive/larger numbers.
  • Ren’Py reserves the use of files that begin with 00 (so, don’t call your file 00statements.rpy, but 01statements.rpy is fine). You can read more on the “reserved” names in Ren’Py here: https://www.renpy.org/doc/html/reserved.html
  • Aside from initialization offset adjustments, files are read in alphabetical order (including folder names – so, Xia Route/bad_end.rpy is loaded after Ashwin Route/normal_end.rpy even though bad_end.rpy would come before normal_end.rpy alphabetically, since Ashwin comes before Xia for the folder name).

Reaching the end of a file acts like a return statement

Normally in Ren’Py, code will “fall through” to a later label if no jump, call, or return statements are provided. For example, consider the execution order of the following code:

# This code is in script.rpy
label start():
    "The game starts here."
label my_label1():
    "This is line 2."
label my_label2():
    "This is line 3."

When played in-game, this will display “The game starts here.”, “This is line 2.”, then “This is line 3.”, and then the game will end. Although there is no explicit jump to get from the start label to my_label1, Ren’Py will continue on with the code below the line “The game starts here.” until it reaches a jump, call, return statement, or the end of the file.

The end of the file acts like an implicit return statement, which is why the game ends after the line “This is line 3.”. Ren’Py does not automatically jump to code in other files. If you had a label in a file called script2.rpy like so:

# This code is in script2.rpy
label my_label3():
    "This is line 4."

and you want to jump to my_label3 after my_label2, then you must include a jump to it at the end of my_label2 i.e.

# This code is in script.rpy
label start():
    "The game starts here."
label my_label1():
    "This is line 2."
label my_label2():
    "This is line 3."
    jump my_label3 # New!

Even though my_label3 exists in a different .rpy file, Ren’Py will find it and jump to it after displaying the line “This is line 3.” Without this jump, the game would end.

Organizing Audio

By default, Ren’Py will generate an audio folder for you inside the basic game folder (i.e. game/audio/). If you put audio files in here, Ren’Py will auto-import them into something called the audio namespace, which will let you play the sounds without writing out the whole file name.

For example, if you have a file Evensong.ogg inside game/audio/ (i.e. game/audio/Evensong.ogg), then you can use either of the following statements to play it in-game:

# Direct method; includes full file path and extension in a string
play music "audio/Evensong.ogg"
# Auto-import method; name is forced to lowercase, no folder or extension
play music evensong

You can organize your audio files into subfolders as well. For example, say you have a file located in audio/sfx/glass_crash.ogg:

# Direct method; requires full path
play sound "audio/sfx/glass_crash.ogg"
# Auto-import; no folder/extension
play sound glass_crash

There are some things of note when using the auto-imported names: your file name must follow the same rules as variable names (aka snake_case, no spaces, no dashes, etc, only letters, numbers, and underscores, can’t start with a number), and it must use a supported audio file format. Of the supported audio formats, I typically recommend .ogg.

You can find more information on this in the Ren’Py docs. For the most part, the auto-import feature works well for basic audio files. Otherwise, you can declare your own values in the audio namespace to play (not covered in this tutorial).

Organizing Images

Like with audio, Ren’Py comes with an images/ folder inside the game folder. You can make as many subfolders inside this folder as you like. Ren’Py has a different set of rules for auto-importing images, as images have a “tagging system” which uses spaces. You should almost always be using image tagging for images you show in-game like sprites and backgrounds.

If you’re relying on Ren’Py auto-importing image names, it will do the following:

  • All sub-folders inside the images/ folder are left out of the image name
  • The extension is omitted
  • The file name is forced to lowercase
  • Spaces and underscores are preserved as-is. File and folder names should only consist of letters, numbers, underscores, and spaces

Some example file names and their auto-imported image names:

  • images/Characters/Xia/Xia Happy.png → xia happy
    • Remember, all the folders are left out, as is the extension. So all that’s left is “Xia Happy”, which gets forced to lowercase, resulting in just “xia happy” as the image name.
  • images/bgs/bg_restaurant.jpg → bg_restaurant
    • The underscore is preserved, so this particular image doesn’t take advantage of Ren’Py’s tagging system. It would be more common to call this image “bg restaurant.jpg” instead so the image name is “bg restaurant”.
  • images/Characters/Aswhin/WORK OUTFIT.webp → work outfit
    • Although this image is stored in the “Ashwin” subfolder, that information is lost when Ren’Py is automatically declaring the image names. If you also had images/Characters/Zoran/Work Outfit.png, notably, its automatic image name would also be “work outfit”, resulting in a conflict. In the end, the image “work outfit” would refer to images/Characters/Zoran/Work Outfit.png, because it comes after images/Characters/Ashwin/WORK OUTFIT.webp alphabetically.

Discussing image tagging is outside the scope of this tutorial, but know that you should generally be using folders to organize your images. I suggest having a folder for backgrounds and one folder for each character sprite at minimum. I also suggest keeping UI images in the gui/ folder. You can make your own subfolders in the gui/ folder as well as required, though images in the gui/ folder do NOT get auto-imported image names, so you need to write out the whole filepath (starting with gui/ and ending with the extension) to use them in your screens.

Summary

  • The answer to the question “Where do I put that code?” is usually “wherever you will think to look for it later”.
  • You can make as many .rpy files in as many sub-folders as you like. They will be compiled into something that functions like one giant file.
  • If you rename or delete a .rpy file, you have to rename or delete its corresponding .rpyc file as well.
  • Initialization order depends on the file’s alphabetical order, any init offsets, and some specific statement orders (e.g. default happens after define).
  • Game script execution order depends on where you put control statements like jumpcall, and return. You can freely jump and call labels in other files.
  • You can freely use variables, images, functions etc. that you set up in one .rpy file in any other .rpy file (as long as it was declared during init time – definedefaultimage, and anything in init: or init python: blocks are set up during initialization, for example).
  • Reaching the end of a file acts like hitting a return statement (and usually ends the game).
  • Put your images and audio into subfolders as well for better organization

Next Steps

You can look at the Ren’Py documentation on Labels & Control Flow for some examples of how to use jump and call to alter the execution order of your script, or read up on Displaying Images to learn more about image tagging and displaying images in your game. You might also consider Lezalith’s Tutorials on these topics as well.

Leave a Reply