Make an account to claim $5 in free credits.Claim →
Blog

Animations

Andrew Pareles (@Kubester)

When building Kube.new, it was completely up to us to decide how you should be able to animate objects. This blog is about how we designed our animation system.

Skeleton Animators

In most places online, people animate characters using Skeletons. Skeletons are essentially just collections of points you move around. Your character follows the points in the skeleton.

So we created a Skeleton animator - an object that simply moves around a Skeleton. It points to three objects: 1. the Skeleton it should animate, 2. the animation to play, and 3. a Clock object that lets you start and stop it.

Here's what it looks like: (And here it is in our API: SkAnimator)

how skeleton animations are organized

All we need next is a way to attach an object to that Skeleton to move along with it. We did that by adding the SkAttachment type which lets you "glue" items onto the Skeleton. skattachments - how to attach objects to a skeleton

We were happy to find the AI responds well to this format! Specifically Claude. AI has seen millions of Blender files online. Our implementation is based directly on GLTF files, which are the open source standard for storing skinned meshes (skeleton-based). This format is clearly similar enough to our implementation to have some transfer.

Property Animators

We also wanted to support general animation, not just animation based on a Skeleton.

To do this, we introduced PropertyAnimator. It is very similar to the Skeleton Animator above, except it takes in the name of any field you want to animate, instead of pointing to a Skeleton.

how property animations are organized

You can animate any field, as long as it's a continuous number (position, velocity, color, texture width, anything!).

You can also smoothly transition a value to a specific point. We expose tweenTo(), which creates a Tween, which is an basically just PropertyAnimator with just two keyframes, but is cheaper over the network and and deletes itself when it's done. This lets you animate position, velocity, color, etc.

Blending

In order to deal with multiple animations on the same object (like Walking, Jumping, Idle on one character), we let each animator specify a priority and weight field. The animations are blended naturally in order of priority, until the total weight reaches 100.

For example, if you want to smoothly transition your character from Walking to Jumping, all you need to do is have two SkAnimator objects pointing to it, and transition the Walking weight down to 0 and the Jumping weight up to 100. We expose the smoothlyChangeWeight() function in TS so that this is easier. It literally updates every frame (not just per physics tick), so transitions happen very smoothly (240fps+).

Clocks

The Clock object is used internally by both animators. It is a special timing type that lets the server and client agree exactly when the animation started/stopped. It uses timestamps under the hood, which we discussed in our Networking blog.

rs
// Simplification:struct Clock {    started_at: f32,                // a timestamp    clock_location: ObjectLocation, // either Server or Client    should_loop: bool,     speed: f32,       }

High Framerate

Both Animator types we discussed above are recognized by our shaders, which means they can play at very high framerates (eg 120-240fps!) instead of just 60fps, which we'd have if we just implemented animations to be on physics ticks. We always deduce the position of the Skeleton/Property per frame, not just per tick!