We start in Blender, where I used geometry nodes to help me visualize and arrange “Capsulites,” a procedural shape I define with five numbers: two radii, a length, a squareness, and an aspect. I made every part from this basic shape, then exported a list of positions, rotations, and those five numbers. In the game, I can recreate the same shapes with the same procedural code.
You can see the exported data in this file in the source repository. I wrote a quick and very ugly Blender exporter to create this TypeScript file. The Capsulites are exported as an array of numbers. The enums in that file give me the indices for each part in each model, letting me address them by name elsewhere in the code.
To fit inside 13k, the animation had to be procedural. I’ve worked as a 3D animator, so I’m familiar with animating by hand and could work backwards from there to figure this out. The keys and interpolation I’d set by hand for most of these bones would resemble sine waves, so I started there. For each unicorn part, I rotated it in Blender to the midpoint of where I wanted the sine-wave action, then specified the magnitude of the sine wave that would drive it in code.
Straight sine waves lacked weight, so I experimented until I found smoothstep(0.5 + 0.5 * sin(x)) * 2 - 1, which preserves the −1 to 1 range, but holds for longer at the extremes of the arc. That pause is “anticipation” in animation terms. It takes the legs from flapping in the breeze, to a “and a one, and a two” rhythm, like he’s gearing up for the effort of swinging them back and forth.
The next job was working out phase offsets: how far ahead of or behind the others each part should be in its wave. This was mostly a lot of guesswork followed by refining, but the two applicable animation principles are overlap and follow-through. A simple way to think about the legs is that they unfurl like a flag: each part lower down the chain is a little behind its parent as the wave travels down the leg.
Once the legs individually made sense, I offset them from each other to get the rhythm: back two, clop-clop, then front two, clop-clop, then glide.
You can see my final set of offsets and magnitudes for each bone in this table in the code, and just below it you can see the bit of code that just loops through every bone and sets the rotation to the modified sine wave.
Then I just had to add a little vertical and horizontal motion to sell the effort the unicorn is putting in. By pushing off the ground, you can feel his weight, and by falling behind then surging forward, you can feel the push-off from the legs. Here I plugged in one of the procedural stalwarts, abs(sin(x)), which looks a lot like a bouncing-ball animation curve. I stagger the vertical and horizontal motion a little to give the landing extra oomph. In the next video, the top is just sin(x); the bottom is abs(sin(x)). Amazing what some velocity discontinuity does, right?
That’s the whole trick. Arrange the starting pose at the midpoint of the motion, then add offset and scaled sine waves to all the bones. In Panzercorn and Uniphony, the unicorns just glide along undulating flight paths, looping that animation at different speeds. Behold my goodest boy!