Skip to content

Core Concepts: Timelines, Keyframes, and Channels

Mathéo Auer edited this page Oct 20, 2025 · 1 revision

Core Concepts: Timelines, Keyframes, and Channels

At its core, TimelineFX is about changing values over time. This page explains the hierarchy of classes used to define these changes, from the high-level Timeline down to the individual Keyframe.

The Hierarchy of Animation

An animation is defined by three main components:

  1. Timeline: The top-level object that holds all animated properties for a complete effect.
  2. TimelineTrack: Represents the animation of a single property (e.g., "radius") over the entire duration.
  3. PropertyChannel: A segment of a track that contains the actual Keyframe data and defines interpolation.

1. Timeline and TimelineProperty

A Timeline is built using a TimelineBuilder and acts as a container for multiple tracks. Each track is associated with a TimelineProperty<T>, which is essentially a type-safe identifier for an animated value.

The name you give to a property (e.g., "effects.main.radius") is arbitrary. It's just a key. The magic happens when you later bind this property to an actual field in your effect class.

// Define properties with unique string IDs
TimelineProperty<Double> radius = TimelineProperty.of("radius");
TimelineProperty<Vector3d> position = TimelineProperty.of("particle_offset");

Timeline myTimeline = Timeline.builder()
    .doubles(radius, track -> { /* ... */ })
    .vector3d(position, track -> { /* ... */ })
    .build();

2. Keyframe

A Keyframe is the most fundamental building block. It marks a specific point in time and assigns a value to it.

// A keyframe at 2.5 seconds with a value of 10.0
Keyframe<Double> kf = Keyframe.of(2.5, 10.0);

3. PropertyChannel and Interpolation

A PropertyChannel is a collection of keyframes that defines how a value changes over a specific time segment. When the animation time falls between two keyframes, the channel interpolates the value.

You can control the "feel" of this interpolation using curves.

Easing and BezierCurve

By default, interpolation is linear. You can change this by applying a TemporalCurve to a keyframe, which affects the transition from that keyframe to the next.

  • Easing: Provides common, predefined curves like EASE_IN, EASE_OUT, and EASE_IN_OUT.
    .keyframe(1.0, k -> k.value(10.0).easing(Easing.EASE_IN_OUT))
  • BezierCurve: For complete control, you can define a custom cubic Bézier curve, similar to those found in CSS or animation software.
    // Creates a curve with a strong "ease-out" effect
    .keyframe(1.0, k -> k.value(10.0).bezier(0.25, 1, 0.5, 1))

4. preBehavior and postBehavior (Extrapolation)

What happens when the animation time is before the first keyframe or after the last one in a channel? This is controlled by extrapolation behaviors.

  • HOLD(Default): The value of the first/last keyframe is held indefinitely.
  • LOOP: The channel's animation restarts from the beginning.
  • PING_PONG: The channel's animation plays in reverse.
Timeline timeline = Timeline.builder()
    .doubles(radius, track -> track
        .segment(0.0, channel -> channel
            // This 2-second animation will loop forever
            .postBehavior(Extrapolation.LOOP)
            .add(Keyframe.of(0.0, 0.0))
            .add(Keyframe.of(2.0, 1.0))
        )
    )
    .build();

◄ Back to Core Concepts