Expand description
Ease curves and tweens, in integers, drawn from the time alone.
An ease curve (Ease, or any Easing) bends progress: it takes how
far through something is, in 1/65536 (ONE is the end), and gives how
far along the value is. A Tween is a value going from one end to the
other over a time, along a curve. Lerp is how a type goes between
two of its values.
use shuyi_core::{Ease, Family, Tween};
// A score that pops: it rises 6 pixels in 300 milliseconds, overshoots
// a little and settles.
let pop = Tween::new(0, -6, 300).with_ease(Ease::Out(Family::Back));
assert_eq!(pop.value_at(0), 0);
assert_eq!(pop.value_at(200), -7); // past its end: the overshoot
assert_eq!(pop.value_at(300), -6);
assert_eq!(pop.value_at(5000), -6);§Drawn from the time alone
A tween stores no progress. Its value is a function of its fields and
the time since it started (Tween::value_at), so a tween never needs
updating, and replays, saves and a test of any moment are exact. A tick
simulation stores the tween and the step it started on; a view stores it
and the millisecond.
Its time is a plain u32 whose unit the caller picks: steps in a tick
simulation, turns in a turn game, whole milliseconds in a view. A screen
gets each frame’s time in seconds, so a view counts its own
milliseconds. A caller with a u64 clock passes the time since the
start as u32::try_from(now.saturating_sub(start)).unwrap_or(u32::MAX).
“It landed” is Tween::finished_between the previous frame’s or
step’s time and this one’s, true once, with no flag kept.
§The curves
Ease names Robert Penner’s curves as easings.net lists them: ten
Familys, each eased in, out, in and out, or out and in, with
Linear, smoothstep (Smooth) and
Steps. In RON a curve is written Linear,
InOut(Cubic) or Steps(4).
Out(f)isONE - In(f)(ONE - p), for every family.InOut(f)isInover the first half andOutover the second, except forBackandElastic, whoseInOuton easings.net has constants of its own.OutIn(f)isOutover the first half andInover the second.BackandElasticovershoot: they go below 0 or aboveONEon the way.
Every curve gives exactly 0 at 0 and exactly ONE at ONE, is
within 1/65536 of its formula for Linear, Smooth, Steps and Quad
to Quint, and within 2/65536 for the rest. The curves are computed in
integers, so they are the same on every machine; a skin that works in
floats eases through shuyi_ui::ease::ease_f32, which goes through
these.
The same curves in other engines, checked against their docs on
2026-11-04 (Godot 4.7’s Tween, Bevy 0.20.1’s EaseFunction in
bevy_curve, Defold 1.14.0’s go.animate, MDN’s <easing-function>).
A family and a way of easing make the name: InOut(Cubic) is
easings.net’s easeInOutCubic, Godot’s TRANS_CUBIC with
EASE_IN_OUT, Bevy’s CubicInOut and Defold’s go.EASING_INOUTCUBIC.
Family | easings.net | Godot | Bevy | Defold |
|---|---|---|---|---|
Sine | Sine | TRANS_SINE | Sine | SINE |
Quad | Quad | TRANS_QUAD | Quadratic | QUAD |
Cubic | Cubic | TRANS_CUBIC | Cubic | CUBIC |
Quart | Quart | TRANS_QUART | Quartic | QUART |
Quint | Quint | TRANS_QUINT | Quintic | QUINT |
Expo | Expo | TRANS_EXPO (1) | Exponential (2) | EXPO |
Circ | Circ | TRANS_CIRC | Circular | CIRC |
Back | Back | TRANS_BACK | Back (3) | BACK |
Elastic | Elastic | TRANS_ELASTIC | Elastic | ELASTIC |
Bounce | Bounce | TRANS_BOUNCE | Bounce | BOUNCE |
Ease | easings.net | Godot | Bevy | Defold | CSS |
|---|---|---|---|---|---|
In(f) | easeIn… | EASE_IN | …In | IN… | none |
Out(f) | easeOut… | EASE_OUT | …Out | OUT… | none |
InOut(f) | easeInOut… | EASE_IN_OUT | …InOut | INOUT… | none |
OutIn(f) | none | EASE_OUT_IN | none | OUTIN… | none |
Linear | none | TRANS_LINEAR | Linear | LINEAR | linear |
Smooth | none | none | SmoothStep | none | none |
Steps(n) | none | none | Steps(n, JumpAt::End) | none | steps(n, jump-end) |
- Godot’s
TRANS_EXPOis Penner’s first version, shifted by a thousandth so it has no jump: within 1/1000 ofExpo. - Bevy’s
Exponentialcurves are scaled to meet 0 and 1 without a jump: within 1/1000 ofExpo. - Bevy’s
BackInOuttakes its constant as1.70158 + 1.525, not1.70158 * 1.525, so it overshoots further thanInOut(Back).
None here matches Godot’s TRANS_SPRING, Bevy’s Elastic(omega),
SmoothStepIn, SmoothStepOut and SmootherStep curves, or CSS’s
named curves (ease, ease-in, ease-out, ease-in-out), which are
cubic Béziers. CSS’s step-end is Steps(1).
§Curves and tweens are data
Ease, Tween and Repeat read and write with serde, inside a
game’s own RON files. A tween written with only from, to and
duration takes the defaults for the rest:
use shuyi_core::{Ease, Family, Tween};
let tween: Tween<i32> = ron::from_str("(from: 0, to: 10, duration: 300)")?;
assert_eq!(tween, Tween::new(0, 10, 300));
let eased: Tween<i32> =
ron::from_str("(from: 0, to: 10, duration: 300, ease: InOut(Cubic))")?;
assert_eq!(eased.ease, Ease::InOut(Family::Cubic));Structs§
- Tween
- A value going from
fromtotooverduration, along a curve, afterdelay: drawn from the time since it started alone (value_at). The module docs say how a simulation and a view keep one.
Enums§
- Ease
- A named ease curve. The module docs name each beside other engines’ names.
- Family
- The ten families of Penner’s curves, as easings.net names them, from the gentlest to the ones that overshoot and bounce.
- Repeat
- How many times a
Tweenplays, in all.
Constants§
- ONE
- The whole way, in 1/65536: progress runs from 0 to
ONE.