RiggyTry it

Spine 4.2 Bezier curves in JSON, explained

The curve field is the most confusing part of the Spine animation format, and the official format page in places still describes Spine 3.8, where it meant something else. Here is what the numbers mean in 4.x, with frames taken from the official example exports.

Three kinds of curve

A key's curve describes the way from this key to the next one, so a curve on the last key is ignored. It comes in three forms:

  • no curve at all — linear interpolation;
  • "stepped" — the value holds until the next key and then jumps;
  • an array of numbers — a cubic Bezier, four numbers per component of the value.

An empty key {} is valid too: time 0 and the default values of its timeline. Omitted values fall back to the timeline's default, not to the previous key.

The four numbers are absolute

For each component the array holds [cx1, cy1, cx2, cy2] — the two control points of the curve that runs from (time1, value1) of this key to (time2, value2) of the next. In 4.x they are absolute: cx1 and cx2 are seconds and must stay inside the segment, time1 ≤ cx1 ≤ cx2 ≤ time2; cy1 and cy2 are values in the timeline's own units — degrees for rotate, pixels for translate, 0 to 1 for color components and mixes.

A rotate swing from the celestial-circus example:

[
  { "value": -13.18, "curve": [0.607, -13.18, 0.733, 15.86] },
  { "time": 1.1, "value": 15.86, "curve": [1.707, 15.86, 1.833, -13.18] },
  { "time": 2.2, "value": -13.18 }
]

In the first segment, 0 s to 1.1 s and −13.18° to 15.86°, cy1 equals the start value and cy2 the end value: flat tangents at both keys, the classic ease-in-out pendulum.

Four numbers per component

A timeline with several components needs a quadruple for each, in order — even for a component that does not change. Stepped is one string for the whole key; linear, stepped and Bezier cannot be mixed within one key.

Length of the curve array by timeline
TimelineComponentsNumbers
rotate, translatex, scaley, alpha…value4
translate, scale, shearx, y8
rgbr, g, b12
rgbar, g, b, a16
deformprogress 0 → 14

A two-component translate from the coin example: x eases from −8.3 to 0, while y stays at 0 and still gets its own flat quadruple.

{ "time": 0.6696, "x": -8.3,
  "curve": [0.794, -7.08, 1.167, 0,   0.794, 0, 1.167, 0] }

Deform is the odd one: its value is a single progress from one mesh shape to the next, so its curve is always four numbers and its cy runs from 0 to 1, whatever the vertices do.

Stepped

Stepped holds a pose, or switches instantly. The coin flips its color with two keys almost on the same time, the first one stepped:

{ "time": 1.3318, "color": "858585ff", "curve": "stepped" },
{ "time": 1.3333, "color": "858585ff" }

Why 3.8 curves break in 4.2

In Spine 3.8 all four numbers were normalized to 0…1 — shares of the time interval and of the value change, exactly like CSS cubic-bezier — and one curve served all components. The current encoding came with 4.0. Paste normalized numbers into a 4.x file and they are read as seconds and degrees: the file loads, and the animation jerks. To convert a CSS easing or a 3.8-style curve, use the cubic-bezier to Spine 4.2 curve converter.

Recipes, and one trap

  • Linear — leave curve out.
  • Linear as an explicit Bezier — control points on the chord: cx1 = t1 + Δt/3, cy1 = v1 + Δv/3, cx2 = t1 + 2Δt/3, cy2 = v1 + 2Δv/3.
  • Ease-in-out — cy1 = v1, cy2 = v2, with cx1 and cx2 at about a third and two thirds of the segment. The editor's default curves look like this.

The trap: stretching an animation in time moves the curves too. The first and third numbers of every quadruple are times, so scaling the key times and forgetting the curves leaves control points outside their segment — the runtime draws jerks instead of an error. The Spine JSON validator flags such handles. Which curves animators actually use, and where they leave keys linear, is measured in Idle animation timing, measured on 127 official Spine animations.

Exact curves from your spec

Riggy takes Bezier quadruples copied from Spine or from your spec as they are. Sign up and drop a PSD.

Animate your own PSD