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.
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):
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.
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.
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
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."
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.