Cuttlebone Retrospective #02- Pipelines

Welcome back! In this part of the series of retrospective posts about the making of Cuttlebone, I’ll be talking about a cool and very important topic in the development of visual novels, though its concepts apply to making any kind of game, really - and that is pipelines.
By pipelines I mean the process with which you take the assets you make, from the program where you create them, into the game in a fully functioning final state. To illustrate, I will explain the problem through the standpoint of the portrait art/sprites work. It’s a little technical but it should be easy to understand. Alright, let’s dive in!
Recognizing the problem
Let’s take our heroine, Inami, as the test subject. In terms of assets, Inami is a character comprised of 2 poses (upright and leaning forward), 2 costumes (casual clothes and swimsuit), with 8 unique expressions on the first pose and 4 unique expressions on the second pose, displayed at 3 different levels of zoom to signify how close she is to Shu, the point-of-view character. Fairly standard stuff.
Inside of the program where these assets were made - Clip Studio - each of these elements is made up of bundles of folders and layers neatly arranged. However, what you see in the game at any given time is only a single image, right? So, the clear way to arrive at our goal is to render every logical unique combination of these layers as a single image and declare them all in the game code. Easy enough.
Once you actually start doing this though, you’ll notice a towering problem. Let’s take a look back at Inami. How many individual images would we have to render to get all of her sprites?
So we have 2 poses, and each pose has 2 costumes, and one of the poses has 8 expressions, the other has 4, and all of this is displayed at 3 different levels of scaling for the different zooms we want to have.
Simplifying this, we get:
Total # sprites = 2x8x3 + 2x4x3 = 48 + 24 = 72 (!!) unique portrait art images.
That is a craaaazy amount of soul-draining work, for just this one character. And consider this, what if you suddenly wanted to make a tiny change to her clothes in pose 1? You’d have to re-render 24 images! Oh mercy…
Also note that to render each of those you’d have to carefully go in and hide the irrelevant layers, show the relevant layers, render them, then go back in and crop the images from 2 of the zooms so that her eyes line up with the horizon in the backgrounds and the perspective is correct. And make sure you don’t accidentally make a mistake, or it’s back to doing it all again.
Yeah.
This is a clearcut example of a bad pipeline. I was spending huge percentages of my time doing mind-numbing non-creative work that I shouldn’t have to. That brings us to the next step.
Improving the pipeline
We’ve identified a problem, the tricky part now is crafting solutions. To do that, we have to get smart (or ask for help!)
The major improvement I used in Cuttlebone, that turned out to be an awesome time saver, was to make Ren'py (the engine) dynamically combine the body with the expressions. So instead of rendering every logical unique combination of Inami, I was just rendering the poses with the costumes, and then the expressions separate. Ren'py, through a bit of code, would then overlay the expressions on top of the faces to achieve the exact same look.
Doing this I now only had to render: 2x2x3 + 8x3 + 4x3 = 48 unique images! Down from 72. That’s 1/3rd less work.
Ren'py simply overlays the expression on top of the base and you get the same result, with way less hassle.If we take the example scenario from before, of having to make a change on Inami’s clothes on pose 1, we would now only have to re-render 3 images, as opposed to 24! Massive improvement in this case. How cool is that?
I finished Cuttlebone with the portrait art pipeline in this state. Which wasn’t bad at all in comparison to how it started! But there was still a major problem with it that was eating unnecessary time away from me working on other things - the dreaded zooms.
Diving back in
Notice how even after that major optimization, I was still having to render every single pose and expression 3 times (and crop 2/3rds of those) because of the zooms. This was a problem that I needed to squash if I was to preserve my sanity in the following projects, so I got working on it as soon as the game was out.
I worked on investigating the most obvious method first - scaling the images in-engine. I would just render all of the images once in full size and have the engine scale them to the zooms I wanted.
I found out that Ren'py has a function precisely for this, im.FactorScale, which claims to use bilinear interpolation. After a bit of tinkering, I got it to work. But much to my dismay, the results were far from perfect. Turns out the scaling was causing a significant amount of quality loss. The lineart took a big blow, and the more minute gradients lost their detail. This wouldn’t do.
Reaching out for help in the Lemmasoft forums, I was informed that there was another function in Ren'py I could use, aptly named zoom. Much better image quality this time! But there was still a tiiiiny bit of quality loss that bothered me.
Ultimately I ended up finding a command line application that took the full size images and scaled them with virtually no quality loss. Now we’re talking! So I cooked up a batch script that automated the process for all zooms, and put the scaled images on the game directory automatically, and I finally arrived at my solution to the problem.
Now, I only have to render every pose/costume and expression once, for a total of 16 images, down from the initial 72! Then, I just have to double-click a batch file, wait 5 seconds, and I’ll have them scaled to the 3 zooms with the exact same visual quality I get from Clip Studio, but zero mindless drone work.
Doing that change to pose 1 now is as simple as rendering a single image, clicking a file, counting to five, and it’s fully working in the game. Hell yes.
In case some of you noticed that I skipped over the cropping problem, yes, I did manage to solve that one too. It was relatively simple, I just had to figure out how much each zoom had to be offset by so that the perspective aligned correctly, and apply that to the image declarations in the code.
It took a good bit of work, but with this I’ve saved my future self insane amounts of boring work in future projects, which will now go toward making the actual creative content much better and more polished. Woohoo!
Other pipelines
Although portrait art is a bit of a special case in that it benefits tremendously from good pipelining, some other roles are a tad more optimized (or less complex) by default.
Writing for example, is already great as is due to Ren'py’s splendid scripting system. But, you can still make it more efficient with a few simple tricks, like:
- Use a program that allows quote auto-completion (I personally prefer Notepad++), so that you only ever need to write the opening quotes, and the program automatically writes the closing ones. It will literally cut the amount of times you type a quote by half. That’s nothing to scoff at.
- Shorten the names of your variables. For example, declare your characters by their initials, instead of their full names. ie. Inami can simply be declared by “i”. This makes the script much cleaner and easier to read, as well as saving tons of keystrokes in the long run.
My writing and coding workspace in Notepad++. The second view feature is great!With user interface work, if you’re using Photoshop, you can set actions to automate the exporting of your assets. I find that Photoshop is quite iffy with what it identifies as actions, though. You could also attempt to use it for the portrait art problem I think, if you’re creating those there. In my case I was doing portrait art work on Clip Studio (using some of its vector tools which aren’t compatible with .psd) so I couldn’t use that.
In conclusion
Sadly, there is no mechanical way to improve a work pipeline. Every case is unique and will require its own solution. In the end these are all just optimization problems. But they’re specific types of optimization problems aimed solely toward eliminating needless grind and improving work flow. Which is something that everyone should be concerned with.
Do you have any pipeline/workflow improvement tips you want to share? Please do so bellow in the comments!
Cuttlebone Retrospective #01 - Preface, Writing
Cuttlebone Retrospective #03 - Advice & The Future
You can download Cuttlebone for free here!


