Custom Timeline

A Timeline is the per-bone (or global) keyframe container inside a BlueprintAnimation. Each timeline holds a set of KeyframeInterpolators — one per KeyframeType (position, rotation, scale, script) — and is what the animation system samples when a model is playing an animation. You normally never build a Timeline by hand; the model parser creates them while reading a .bbmodel. This page shows what a Timeline is, how it is constructed, and how an animation made of timelines is played on a model.

Anatomy of a Timeline

A Timeline is constructed with the owning animation and a flag for global rotation:

// animation: the BlueprintAnimation this timeline belongs to
// globalRotation: whether rotation keyframes are in global (model) space
Timeline timeline = new Timeline(blueprintAnimation, false);

It exposes three operations, all keyed by a KeyframeType:

// Whether an interpolator for this type already exists
boolean has = timeline.hasInterpolator(KeyframeTypes.POSITION);

// Get (lazily create) the interpolator for a keyframe type
KeyframeInterpolator<VectorKeyframe, Vector3f> interp = timeline.getInterpolator(KeyframeTypes.POSITION);

// Get (lazily create) the keyframe at a given time, in seconds
VectorKeyframe frame = timeline.getKeyframe(0.0f, KeyframeTypes.POSITION);

getInterpolator and getKeyframe both create-if-absent, so calling getKeyframe is how you add a keyframe to a timeline: it returns the existing frame at that time, or a freshly created one, which you then populate.

Keyframe types

The built-in types live in KeyframeTypes:

  • KeyframeTypes.POSITIONVectorKeyframe / Vector3f
  • KeyframeTypes.ROTATIONVectorKeyframe / Vector3f
  • KeyframeTypes.SCALEVectorKeyframe / Vector3f
  • KeyframeTypes.SCRIPTScriptKeyframe (global only)

POSITION, ROTATION and SCALE are stored on a bone's timeline; SCRIPT lives on the animation's globalTimeline.

Filling a Timeline

A VectorKeyframe value is supplied through IKeyframeData (use DoubleData for a constant value). The per-axis setters are fluent, and a per-axis scale factor lets the parser convert Blockbench units into Model Engine units:

VectorKeyframe frame = timeline.getKeyframe(time, KeyframeTypes.POSITION);
frame.setXFactor(0.0625f)
     .setYFactor(0.0625f)
     .setZFactor(-0.0625f)
     .setX(new DoubleData(x))
     .setY(new DoubleData(y))
     .setZ(new DoubleData(z));
frame.setInterpolation("linear"); // "linear", "step", "catmullrom", "bezier"

Script keyframes are added to the animation's global timeline:

ScriptKeyframe script = blueprintAnimation.getGlobalTimeline().getKeyframe(time, KeyframeTypes.SCRIPT);
script.getScript().add(ScriptKeyframe.Script.from("playsound:entity.zombie.ambient"));

Building an animation from timelines

A bone timeline is registered on its BlueprintAnimation under the bone's UUID; the script timeline is the animation's built-in globalTimeline. This mirrors what the Blockbench parser does:

BlueprintAnimation animation = new BlueprintAnimation(modelBlueprint, "my_animation");

Timeline timeline = new Timeline(animation, false);
// ... fill timeline via getKeyframe(...) as shown above ...

animation.getTimelines().put(boneUuid, timeline); // boneUuid is the BlueprintBone UUID
animation.setLength(2.0);                           // length in seconds
animation.setLoopMode(BlueprintAnimation.LoopMode.LOOP);
animation.setOverride(false);

LoopMode is one of ONCE, HOLD, or LOOP.

Driving a model

You do not sample a Timeline directly. Once a BlueprintAnimation exists on a model's blueprint (registered under its name), you play it by ID through the model's AnimationHandler, and the handler walks the timelines for you each tick:

ActiveModel model = ...;
model.getAnimationHandler().playAnimation(
        "my_animation", // animation ID (BlueprintAnimation name)
        0.1,            // lerp in, seconds
        0.1,            // lerp out, seconds
        1.0,            // speed multiplier
        true            // force restart if already playing
);

To stop it again, use stopAnimation(String) (graceful lerp-out) or forceStopAnimation(String) (immediate).

Updated Aug 19, 2026