Perverteer March 1, 2021 Structuring Your Project: A Ren’Py Howto

Structuring Your Project: A Ren’Py Howto

One of the most daunting things about learning a new framework is knowing where to put everything so that you won’t get completely overwhelmed by the chaos of your own creation in the near future. Reading up on best practices or even cracking open an existing project by another developer and looking at the code will probably provide you some good insights into how things should be done. After over three years developing games in Ren’Py I thought I’d share some of the things I found out that might be helpful to those starting out with the framework.

A blank Ren’Py project gives you a couple of files and directories out of the box. You could just start writing your magnum opus in script.rpy and plonk all those dirty pictures in the images directory and Ren’Py would package a game for you when asked. Starting out like that isn’t much of a problem when writing a short story or creating a demo, but for larger projects some further organization is advisable.

Directory Structure

Folder structure of one of my games.

Ren’Py discovers file references anywhere in the game folder. So if you create an elaborate nested directory structure inside the image folder, you can still reference it by just using the image name in a scene statement in one of your scripts. Imagine having a file in a directory structure like this (which I wouldn’t necessarily advise):

game/images/episode001/day005/scene001/roommate_fucking_doggy.jpg

You don’t have to provide the full path when calling roommate_fucking_doggy.jpg, this would be enough in a Ren’Py script:

scene roommate_fucking_doggy with dissolve

When you start your game, Ren’Py will automatically drill down into those five directories, find roommate_fucking_doggy.jpg and displays the image to the player when they encounter that particular scene statement. The fact that Ren’Py is smart enough to find media automatically provides you with a lot of freedom to structure your assets in a way that still makes it easy to find things at version 0.01, but also when you complete your story at version 1.0.

File Structure

Apart from making a list of file references, Ren’Py also automatically compiles any .rpy files anywhere in the game folder when launching a project. So apart from the standard gui.rpy, options.rpy, screens.rpy and script.rpy, any .rpy file in any nested folder will be considered for inclusion by Ren’Py. So instead of cramming your entire life’s work into script.rpy, you could divide your scripts into episodes or even modularize them by scene, location or whatever makes sense.

A possible structure would be the following:

- game
-- episodes
---- episode001.rpy 
---- episode002.rpy 
---- episode003.rpy 
- characters.rpy
- gui.rpy
- options.rpy
- screens.rpy
- script.rpy

In this instance, character definitions are moved to a separate file, as is the content per episode. script.rpy would only contain the start label required by Ren’Py and jump immediately to the first episode label contained in episode001.rpy.

Adding Variables

Due to the fact that most adult games are funded through subscription platforms like Patreon and SubscribeStar, players expect to play incremental releases. One of the trickier things of releasing incrementally is ensuring save games remain compatible between releases. Of course you shouldn’t worry too much about compatibility when you’ve reworked half of your novel in between releases, you’re delivering a test build, an incremental alpha release, after all. But minor issues have a tendency to throw Ren’Py in a fit, the most common problem being undefined or missing variables.

The common way to define a variable in Ren’Py is like this: $ roommate_fucked = True This line sets up the roommate_fucked variable which can later be used in conditionals. You can use this anywhere, but if you litter your script with variable declarations, it will be hard to keep track of them. It might seem easy at v0.1, but you’ll be sorry when you need to hunt for a variable you’ve declared somewhere eight versions back.

A better way would be to declare all of your variables at the top of the file where they’ll be used:

    
label episode2:
    $ roommate_fucked = False
    $ roommate_stroked = False
    $ roommate_diddled = False
    $ roommate_ignored = False

    "Look, it's my roommate, she's so hot."

    menu:
        "Fuck roommate":
            $ roommate_fucked = True
            mc "Hey, wanna fuck?"
            roommate "Sure thing."
            # Lots of fucking
        "Stroke roommate":
            $ roommate_stroked = True
            roommate "Stop stroking my hair, you creep!"
            # Fistfight ending in hospitalization
        "Diddle roommate":
            $ roommate_diddled = True
            roommate "I love it when you play with my nipples like that in a platonic way."
            # Late-night discussion on the merits of Dostoevsky's The Brothers Karamazov
        "Ignore roommate":
            $ roommate_ignored = True
            "Nah."
            # Lonely masturbation
        

Declaring variables like above has one problem though, it breaks backwards compatibility with saves when you introduce new variables in old code. Consider the scenario where a user has saved the game at the end of v0.1. Ren’Py saves the game state and all variables declared at that point.
For v0.2 you decide to introduce a couple of additional variables to the code you’ve written previously, to better track certain choices in the game. You reference those newly defined variables in the new story content in v0.2 and that’s where the problems begin. Players who load a v0.1 save will hit undefined variable errors at that point, which can be ignored, but lead to a rather disjointed play through.

Luckily, Ren’Py has a way of declaring variables in a way that doesn’t break saved games. Enter the default statement. Prepending default to a variable declaration will make sure Ren’Py loads that variable before the game starts and populates it with the value of your choice, thus ensuring saved games will have that variable defined as well. The example from earlier will look like this now:

    
label episode2:
    default roommate_fucked = False
    default roommate_stroked = False
    default roommate_diddled = False
    default roommate_ignored = False

    "Look, it's my roommate, she's so hot."

    menu:
        "Fuck roommate":
            $ roommate_fucked = True
            mc "Hey, wanna fuck?"
            roommate "Sure thing."
            # Lots of fucking
        "Stroke roommate":
            $ roommate_stroked = True
            roommate "Stop stroking my hair, you creep!"
            # Fistfight ending in hospitalization
        "Diddle roommate":
            $ roommate_diddled = True
            roommate "I love it when you play with my nipples like that in a platonic way."
            # Late-night discussion on the merits of Dostoevsky's The Brothers Karamazov
        "Ignore roommate":
            $ roommate_ignored = True
            "Nah."
            # Lonely masturbation
        

Handling choices

Choices are one of the things that define a visual novel as a genre. Ren’Py offers the menu structure outlined above in the code example to deal with player choices. Based on the player response one of the four variables will be set to True. Of course, those choices could easily increment the value of a character attribute (like lust, corruption or sluttiness).

    
label episode2:
    default roommate_corruption = 0
    
    roommate "What's that thing?"
    mc "My cock."
    roommate "It's so big and beautiful"

    menu:
        "Fuck roommate":
            $ roommate_corruption += 1
            mc "I'm going to fuck you with it."
            roommate "Okay."
            # Lots dick utilization
        "Decline roommate":
            mc "Sorry, I have a headache."
            roommate "Okay."
            # You play several rounds of Monopoly together

    if roommate_corruption > 0:
       roommate "I'm soooo addicted to my roommate's cock, I just love it."
    else:
       roommate "I feel so chaste, maybe I should become a nun."
        
Choice menu in The Question.

Beware though that implementing a point-based system is going to be complicated to keep track of and is a bit of a balancing act. You’ll probably want to keep a spreadsheet with the maximum values of a certain statistic at certain points in the game in order for your conditionals to make sense and not lock players out of certain scenes or story branches entirely. A simpler system based on Boolean variables might make more sense, unless you relish the abstractness of numbers.

These are some of the pointers I hope are of help to beginning developers wanting to use Ren’Py as their new development framework. Having some basic knowledge of programming and Python is a plus, although Ren’Py is pretty easy to get into.

Care has been taken to compile this guide. If you spot any mistakes, or if anything is unclear feel free to drop a comment below or join us on our Discord server.

Perverteer

I create adult visual novels, with the help of a couple of awesome volunteers who test and proofread my work. My projects include Sisterly Lust (completed) and Tales From The Unending Void (in development). Both are available for Windows, Mac, Linux and Android.

Subscribe
Notify of
guest
6 Comments
Newest
Oldest
Inline Feedbacks
View all comments
NaughtyRoad
Member
NaughtyRoad(@naughtyroad)
9 months ago

Neat write up – stuff in there that I wish someone told me when I first started. 😀

I’d like to suggest one thing though that comes even before all this: get some source control, or a cloud file backup system that keeps at least a few old versions of your files in history!

You won’t actually know how much you need it until you do and then, well, if you lucky, you lose only days of work. If you’re not… best not think about that. With something in place, you’re always able to pick up right where you were.

Manka Games
Member
Manka Games(@mankagames)
9 months ago
Reply to  NaughtyRoad

Yes, VCS setup should be the first step for every project. And commits must be as atomic as possible. It must be a rule: Made a feature — commit and push it on the server.

PixelRepublic
PixelRepublic(@pixelrepublic)
9 months ago
Reply to  NaughtyRoad

I would suggest Git for that, and more specifically Gitlab; free hosting with private repos.

We use it for code hosting and automatic build & deployment for LewdPixels.com.

It might be an idea for us to put out a guide on it.

NaughtyRoad
Member
NaughtyRoad(@naughtyroad)
9 months ago
Reply to  PixelRepublic

I absolutely love gitlab for it’s intuitive interface and the way it’s quick to set up, but the 10GB repo size limit mean I can’t use it for my ren’py+daz3d projects (for Light of my Life, I hit that limit around the 6 month mark; all that graphical data adds up).

Azure DevOps allows larger repos, but it’s pretty difficult to set up if you’re unfamiliar with it (don’t even get me started on the Amazon offering, that requires some serious IT skills).
GitHub might be a better option for a starting dev in that case, but I think you might need a premium account to get over that limit (I haven’t really looked in to it tbh).

But if you’re shelling out cash anyways, something like backblaze or carbonite might be better options for users unfamiliar with git. Although nothing beats a true source control system like git imho, I realize you gotta pick your battles as a dev.

Manka Games
Member
Manka Games(@mankagames)
9 months ago
Reply to  NaughtyRoad

Check AWS CodeCommit, there is 50Gb size limit and 6 cents per month per GB over this limit. And as lifehack, you may divide your repository into subrepositories, this may help to evade limits 🙂

Upd. My bad, I didn’t read your note about Amazon

Last edited 9 months ago by Manka Games