Like the Corners of my Mind
I just realized that despite all the fuss we make about amnesia in Voodoo Detective, I never took the time to share how the game manages memory! In this blog post, I set out to remedy this gross oversight.
Having said that, what I write here is likely to be more technical in nature than what I’ve shared previously. So please, feel free to skip this one if you’re not interested (i.e. you’re too CHICKEN to come play with me in this technical wonderland).
But if you do continue reading, I’m going to assume that you know what computers are, what computer memory is (RAM), and what persistent storage (hard drives) is.
And if you do stick around, I promise this will be a memorable one. You’ll not soon forget this blog post. It’ll be a journey to remember. And so on.
Animation
Ones, Twos, and Threes
The single most gluttonous consumer of memory in Voodoo Detective is our sprite animations. As such, I’ll spend most (all) of this post talking about how I optimized animation memory usage. But first, here’s a little background on how animation works.
By displaying an ordered sequence of still images (frames), one after another, we’re able to simulate motion. Hand-animated 2D animations will typically display frames at a rate of 24 frames per second (FPS).
But that doesn’t mean each of those 24 frames has to be unique. Oftentimes, to save on effort, animators will repeat every frame twice to produce 12 unique FPS over a 24 frame interval. This technique is called animating “on twos.”
Sometimes to save even more effort, animators will repeat each frame thrice to produce 8 unique FPS over a 24 frame interval. This is called animating “on threes.”
I think this video explains it all much better than any number of words ever could. All character animation in Voodoo Detective’s was done on twos. The only place we used ones was in short bursts during tricky FX animations.
Optimization
I leveraged three main techniques to cut down on our sprite animation memory usage.
Deduplication
I realized early on that since we were planning to animate on twos, I could cut our animation memory usage in half by only storing every other frame of animation. When the animations were played, I could simply repeat each frame twice.
As I put this optimization into practice, I quickly realized that the principle could be further utilized by deduplicating all non-unique frames in any given animation. The perfect example of where this would be most impactful is found in our idle animations. These are the animations where characters are standing still and blinking every so often.
Before Deduplication
After Deduplication
I should note that I didn’t do this manually (that way madness lies). Instead, I wrote software to do everything for me. If you’re curious how that works, please feel free to reach out and I can walk you through it.
Sprite Atlasing
Imagine you have a series of rectangular cookie sheets and a bunch of cookies with different, fun shapes. One cookie is a star, one cookie is shaped like Voodoo Detective, etc. You want to fit as many cookies as possible onto each sheet so that you don’t run out of space in your tiny oven.
This is a perfect analogy for the process of sprite atlasing. Instead of cookies with fun shapes, you have sprites. Instead of rectangular cookie sheets, you have rectangular textures. Instead of an oven, you have RAM.
Sprite atlases are useful for two reasons:
All of our characters are animated on frames with a resolution of 1920x1080, but the majority of those pixels are empty. When this is the case, the shape of your “cookie” isn’t really a rectangle, but whatever shape the non-empty pixels end up being. When you put largely empty frames into a sprite atlas, by ignoring the empty space, you can arrange the sprites much more efficiently. This can cut down on memory usage. But to be fair, image compression algorithms are usually pretty good about taking advantage of unused pixels to compress images. I’m not actually sure what all accounts for the memory savings you’ll see below (possibly a consequence of the compression algorithms being used).
They cut down on the number of draw calls you have to issue to your GPU per frame. Maybe you don’t know exactly what this means. I sure don’t, but suffice it to say: your GPU has to do less work. This isn’t related to memory usage. It’s related to GPU usage. But I mention it for the sake of being thorough. In point of fact, it was the main reason I started using atlases in the first place.
Here’s a single, 7.9 megabyte image.
Here’s that same image placed in a sprite atlas.
Dynamic Loading
The final technique I leveraged in Voodoo Detective to keep animation memory usage down was the dynamic loading of animations. This is actually a whole can of technical worms I could drone on about for hours. If you’re interested in the gritty details of how it was implemented (using Unity’s Addressables library), please feel free to reach out and I’ll be glad to talk your ear off.
Basically, I would only load animations into memory if I knew they were going to be used in the current scene. This may sound obvious, but sometimes it’s a little difficult to know which animations to load. I’ll give you an example.
We don’t need Voodoo Detective’s swimming animation if he’s not near the pool. But even if he’s near the pool, we don’t need his swimming animation if the player doesn’t have swim trunks in their inventory. The state of your current saved game has implications for how we decide to load animations.
Don’t “Forget” to Buy Voodoo Detective
At the time of writing, the Steam Summer Sale 2022 is still going on. If you haven’t already and are interested, please feel free to grab a copy before the sale ends! We’d surely appreciate it, but we’ll love you either way.
Thanks so much for reading!
Love,
Eric Fulton