Value Types and Widgets
Every keyframed value is blended by an interpolator registered for its type, and edited in the Inspector by a widget registered for its type. Both registries are open, and the built-in types use them the same way yours would.
Interpolators
Flow blends number, Vector2, Vector3, CFrame, Color3, UDim, UDim2, NumberRange, and Rect.
Every other type is stepped: a keyframe's value holds until the next one.
To make another type blend, register an interpolator for its typeof name. It receives the two keyframe
values and how far between them to go:
local Flow = require(ReplicatedStorage.Flow)
Flow.Interpolation.Register("NumberSequence", function(a: NumberSequence, b: NumberSequence, alpha: number)
-- Assumes both sequences have the same keypoint times
local keypoints = {}
for index, keypointA in a.Keypoints do
local keypointB = b.Keypoints[index]
table.insert(keypoints, NumberSequenceKeypoint.new(
keypointA.Time,
keypointA.Value + (keypointB.Value - keypointA.Value) * alpha,
keypointA.Envelope + (keypointB.Envelope - keypointA.Envelope) * alpha
))
end
return NumberSequence.new(keypoints)
end)
Register it on every client that plays the sequence, for example from a LocalScript in
StarterPlayerScripts, before the first play. A Property section animating a NumberSequence property, such
as a beam's Transparency, then blends instead of stepping.
alpha usually runs from 0 to 1, but keyframe curves that overshoot, such as Back and Elastic eases or a
Bezier handle outside the box, pass values beyond that range. Clamp inside your interpolator if your type can't
extrapolate. The same interpolator is also what blending
uses to mix overlapping sections.
The editor's preview runs on the plugin's own copy of the runtime, which doesn't run your game scripts, so a type you registered in game still steps in the preview.
Per-component curves
A type made of several numbers can let each component take its own keyframe curve, so a Vector3 can ease on
Y and stay linear on X. Flow.Interpolation.RegisterComponents(typeName, components, blend) registers that:
components lists the component names in the order the Inspector's Component menu shows them, and
blend(a, b, alphas) receives one alpha per component, keyed by name. The built-in types with components
are Vector2, Vector3, Color3, UDim, UDim2, NumberRange, Rect, and CFrame, whose rotation is a
single component. A type without a component blender plays a per-component curve as linear.
Inspector widgets
The editor builds each parameter's Inspector row from the widget registered for its type: number fields, the
color picker for Color3, the gizmo for CFrame, a dropdown for enums, and a plain text field for anything
without one.
A custom section type can register widgets of its own by adding a Widgets table, keyed by type name. The
editor registers them when it loads the module. Widgets are registered by type for the whole editor, so the
example below changes how every NumberRange is edited, not just this section's.
return {
Name = "Spotlight",
Capability = "Sampled",
SubjectClass = "SpotLight",
Parameters = {
Range = { Type = "NumberRange", Default = NumberRange.new(8, 16), Keyframable = true },
},
Widgets = {
NumberRange = function(context)
return context.Components(
{ "Near", "Far" },
function(value) return { value.Min, value.Max } end,
function(numbers) return NumberRange.new(numbers[1], math.max(numbers[1], numbers[2])) end
)
end,
},
Evaluate = function(context, localTime)
return { Range = context:GetParameter("Range", localTime).Max }
end,
}
A widget builder receives a context and returns a handle with an optional SetValue(value), which the editor
calls when the playhead moves. The context provides:
| Member | Meaning |
|---|---|
Type | The resolved type name; Enum.<EnumType> for an enum |
Value | The current value |
Declaration | The parameter's declaration, if it has one |
Parent | The frame to build the widget in; it is cleaned up with the Inspector row |
Commit(value) | Records a new value as one undo step |
Maid | For connections and instances the widget creates outside Parent |
Components(labels, read, write, decimals?) | Builds a row of labeled number fields, the way the built-in Vector3 and UDim2 widgets do |
If a builder errors, that parameter falls back to the plain text field rather than breaking the Inspector.