Last updated on October 7, 2021
After trialing a few different visual novel builders, my team and I settled on Renpy for this game. Using Renpy as our game engine provides most everything we would want to make a visual game very quickly. Speed is a huge plus for us because our team of two artists, our writer and our programmer (me!) are all working on this project in our spare time.
Having multiple people working on game assets can cause conflicts. The renpy game engine example game uses a single file for storing the game script – “script.rpy”. This file is used to direct the flow of the game. You can use it to make characters appear on screen and attribute text to that character. Much like a film or tv script, with the main difference being that this file is source code for renpy. And as a single file, if multiple people are working on it we can quickly get data loss as one person saves over another persons version. You can actually split your script into several files, and renpy is smart enough to stitch all of these together when you launch the game.
To manage version conflicts, I use git for version control. However, the non-tech team had no previous experience with version control software. As a result, I have taken on the role of Canonical Version Decider. I gave each person their own copy of the project in a subfolder ajacent to the main project. This was a simple way for people to get started working on their own parts without them worrying about damaging other peoples work. I could then communicate with team members, and once something was ready I could tidy up the code and merge it with the main project that is being tracked.
While this version control setup requires more manual effort on my part to manage everyone’s commits, our game is small so it is not a problem. The effort of everyone learning and understanding how to use git is outweighed by the time they could otherwise spend making cool stuff. Our dev hours are very limited! I rather have a content in our game than our git log!
The Magic of Spreadsheets
In a recent Galway Game Jam, Angelo from Gambrinous gave a fantastic presentation titled “Spreadsheet Magic”. Unfortunately, due to poor quality audio, it is unlikely that this presentation will become available after the stream. However, Angelo is a generous man with his magic spreadsheet knowledge, so do hit him up on twitter if you have any speadsheet questions. Impressed by his talk, I wanted to see if I could incorporate spreadsheet data into our game as a way of directly affecting game mechanics.
Angelo’s spreadsheet examples took exported data from Unity as inputs and used various formula and techniques to analyse the game. For our game, I wanted the responsibility of data to be in the spreadsheet itself – so that our non-programmers could have a tool to affect the game systems. This would be a lot nicer to and more familiar to use than opening up unfamiliar and scary code editor (which could lead to breaking stuff!).
How would our game’s spreadsheet be different? Our spreadsheet would:
- Contain a table of scores that would be the single source of truth for the main scoring mechanics in the game.
- Allow us to analyse the scoring system, and total scores for each branching path through the game.
It took me a while to figure out the best way to represent the game data. After attempting a few different variations on how to represent our scoring system in a spreadsheet using Google Sheets, I turned to something else. While Google Sheets is powerful, I quickly got bogged down in formulas and found it difficult to get a good representation going. Dissatisfied I though perhaps a database would be better, and went for a kind of hybrid spreadsheet/database using a cloud service called AirTable. After I ported my work to AirTable to see if I could make something a little easier, a nice representation became more apparent. They have a nice way of including column data from other tables, which allowed me to create the relationships I needed. WIth this, I could export a CSV, which I could then put into the game.
Game engine restrictions – Making build.py
First I had to find out how to run arbitrary python in renpy. After doing a few little tests, it turns out renpy has its own subset of the Python standard library available. While it can read and write binary data, it wasn’t as convenient as I would like. Also, from reading through the Renpy support discord, I learned file I/O could slow down the game a lot. Instead of fighting against a limited subset of Python, I decided to part with renpy completly for this step in the process – thus creating a pre-compile step for our game project.
- Step 1: Export data from spreadsheet as CSV and place in project subfolder.
- Step 2: Run build.py using Python cli, which loads in the CSV data file, generates renpy compatible source code, and appends the code to the main script.rpy project file.
- Step 3: Launch the project from Renpy editor, which reads and compiles all the .rpy files as part of the loading time.
- Step 4. (optional) Export the project to target platform.
Extracting scoring moments
Renpy provides us with “labels” as a means of identifying passages of our script. You can “jump” to “labels”. Essentially these are the goto statements of old that can provide us with our interactive-fiction branching narrative structure.
Our games’ scoring system is based on choices. Each choice is contained inside a passage with a given label, and each choice within that passage jumps to yet another label.
The way we handle scoring is by gathering all of the choices inside a label and putting them together in an easy to read ‘passage’ -> ‘choice’ pattern. This means we can update the player score by doing something like this:
player.score += scores['found_porridge']['taste_porridge']
Renpy doesn’t even have to do anything special. It just needs the variable values assigned in the “python init” section of the script.rpy file (or whichever file you put your scripts). The code generated from build.py would loop over the values from the database and construct a multi-dimensional dictionary containing all of the scoring values for the game. By using a well defined naming convention, we can access the scores from the renpy source code without prior knowledge of the exact dictionary keys, as our naming convention will handle that part for us.
So far this is working quite well! Next time I will discuss our little state machine that gives the game it’s “core loop” experience.