Rakit Studios

Home of Rakit Studios : Creators of Convenience Wars


Dealing with sprites…..for a gagillion characters!

This post won’t be to everyone’s taste, in fact there will probably be several who oust me as a rank amateur. That’s OK! I can live with that, “I think”. Truth is everyone does things their own way really and you should know that I put a lot of thought and research into the system I’ll be describing here today. Which by the way produces character sheets like the one below.

Yukito Character Sheet

Yukito Character Sheet

Recently we decided in our VN that we wanted the characters to be more dynamic. We wanted the characters and the backgrounds to feel a lot more like “TV” or even, dare I say it, an Anime! (Shock, Horror!) In light of this, we decided that we wanted the ability to have characters portrayed at three different distances from the camera. For simplicities sake, we’ll call them zo, zm, zi, for Zoom-Out, Zoom-Mid and Zoom-In respectively.

That’s all well and good, but Convenience Wars has around 20 characters. Each character “could”, but probably won’t, have 3 poses. The minor characters will probably only have 1-2, with the major characters having 2-3. There are also outfits, lets say 3 per character. Emotions? We’ll say 8 per character at the moment. Finally the three zoom levels. Do the math on that and you soon have a stupidly large number of sprite images.

There is no way on earth that we’re going to use that many sprites in the game, quite simply because there’s no way on earth we’re going to use every combination of every character, pose, emotion, outfit, and zoom. That would be insane! So I got thinking of a way to automate some of this. We have large source files for the characters, 3000×2000 pixel resolution. Due to some limitations in some of the software we use, we have a file for each pose/emotion/outfit combination. Putting these files in the game assets would be crazy. We considered going down the live crop route also. Further crazyness. So for now we have what we have.

Cropping fun!
There was no way I was going to sit there manually cropping each image for each combination. So I thought, how can I script this. What I came up with was CharBuilder. We define our images like so;

show yuk hi sc em zo

Where, ‘yuk’ is the characters name, ‘hi’ is the pose, ‘sc’ is the outfit, ’em’ is the emotion and ‘zo’ is the zoom level. Why not create something which scans the game files, searching for ‘show’ statements and collects all the unique references for which combination. We then find the source image which has no zooming on it at all, and depending on what zoom level was requested either zi,zo or zm, crop it and resize.

So how do we store that information, it’s different for every character. Each pose will be cropped slightly differenly. Enter JSON. Whilst I was thinking about this, I also decided that CharBuilder would build the character.rpy files for me. In our game we have a layout of ‘game/chars/_charname_/_charname_.rpy’ to store information about that character. I moved that specific information into a JSON file, so that now I can have defaults at the root level, which are common for all characters, and override them for the specifics.

Defaults File
"who_outlines":"[(1, '#ffff')]",
"what_outlines":"[(1, '#ffff', 0, 0)]"}

Yukito’s file
"zo":[[543, 768]],
"zm":[[303, 104], [1942, 1832], [814, 768]],
"zi":[[630, 104], [1254, 1088], [885, 768]]

Notice ‘hi’ under imageattribs, which is a ‘hips’ pose, contains just a width-height tuple. If no cropping is desired for that zoom level, just the width-height of the final sprite is required. However, if a crop is required, we need upper-left and size, followed by a width-height tuple. As a further optimization, you can see that the ‘th’ pose (thumbs) refers to ‘hi’. Looking at the char sheet above, you can see that the two poses, ‘hi’ and ‘th’ are identical apart from an arm movement. This being the case, the cropping will also be identical.

In the end, the script generates folders in the characters folder called, ‘zo’,’zm’,’zi’ and places the images for each zoom level under here, but only the ones that acre actually used in the game. It then also generates the characters .rpy file, an example of which follow.

define y = Character('Yukito' , color='#8b9502', image='yukito', what_slow_cps=say_cts, who_outlines=[(1, '#ffff')], ctc_position='fixed', ctc='cw_ctc', what_color=say_col, what_outlines=[(1, '#ffff', 0, 0)])

image yuk th sc sm zm = 'chars/yukito/zm/yuk_th_sc_sm.png'

With all those codes flying around, it’s kinda hard to remember what is what. That’s why the script also generates character sheets for each character, detailing ‘zi’ versions of each emotion and ‘zo’ versions of each pose and outfit.

In fact in our Git revision control system, we will now only be storing the jsons, and the source images. The character.rpy and the subsequent generated images are considered ‘buildable’. Though obviously when we release the game, we will generate them and archive them into the package.

Well, it was fun explaining this, as much for my own teams sanity as much as my own. Hope you enjoyed a little dig into how we work with our sprites.

cbx33Project Coordinator