#Timeline
Keyframe animation for 2D instances. You write the keyframes, Vision walks them, interpolating each property and firing events on the way past.
local Run = Scope:Animate({
Vision.Keyframe(TweenInfo.new(0, Enum.EasingStyle.Sine), 0, { BackgroundColor3 = White }),
Vision.Keyframe(TweenInfo.new(0, Enum.EasingStyle.Sine), 1, { BackgroundColor3 = Black }),
}, Panel)
Run:Play()
Animate stages the animation and touches nothing - it waits for Play. The
keyframe list is plain data, so the same list can be handed to Animate again
for another instance.
#Animating a vision
Pass a Vision instead of an instance and the keyframes name its values rather than instance properties.
Scope:Animate({
Vision.Keyframe(Ease, 0, { Charge = 0 }),
Vision.Keyframe(Ease, 1, { Charge = 1, Status = "full" }),
}, Interface):Play()
Each write goes through the value, so every handler bound to it runs, every
instance driven by it follows, and a derive reading it recomputes. Value
names are checked when you build the animation, so a name the vision does not
have fails there rather than mid playback.
Keyframe events hand you the vision as their first argument in this mode, where an instance animation hands you the instance.
#Keyframe
Vision.Keyframe(Easing, Time: number, Properties: { [string]: any }, Events: { [string]: { any } }?)
Time is in seconds from the start. Keyframes do not have to be in order -
they are sorted for you.
#Easing arrives at the keyframe
A keyframe's easing describes how the animation gets to it, not how it leaves. The first keyframe's easing is therefore never used, which matches Roblox's own animation editor.
Only EasingStyle and EasingDirection are read from a TweenInfo. Timing
comes from the keyframe times, so Time, DelayTime, RepeatCount and
Reverses are all ignored.
Pass 0 as the time. TweenInfo is here for the shape of the curve, not
its length, and Roblox refuses nil for that first argument.
TweenInfo.new(0, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
#Physics keyframes
Pass a table instead of a TweenInfo and that leg is solved as a spring.
Vision.Keyframe({ Damping = 0.3, Period = 0.4 }, 1, { Rotation = 90 })
| field | meaning |
|---|---|
Damping or Dampening |
damping ratio, under 1 rings, 1 is critical |
Period |
seconds per bounce, defaults to the length of the leg |
A spring is not obliged to arrive on time. When the next keyframe comes round the spring is simply retargeted, keeping the velocity it had, so a run of physics keyframes flows rather than stepping. If it is the last keyframe, the animation stays alive until the spring settles.
Springs need a solvable type - number, UDim, UDim2, Vector2,
Vector3, Color3, Rect, NumberRange. Anything else needs a TweenInfo.
#One track per property
Each property gets its own timeline, built from the keyframes that mention it. A property does not have to appear in every keyframe.
Vision.Keyframe(Ease, 0, { Rotation = 0, BackgroundTransparency = 0 }),
Vision.Keyframe(Ease, 1, { Rotation = 100 }),
Vision.Keyframe(Ease, 2, { BackgroundTransparency = 1 }),
Rotation finishes at one second and holds. Transparency keeps going to two.
A property also spans the keyframes that do not mention it. If colour is set at keyframe one and again at keyframe three, it interpolates across the whole gap - keyframe two is simply not a waypoint for that property.
Vision.Keyframe(Ease, 1, { BackgroundColor3 = White }),
Vision.Keyframe(Ease, 2, { Rotation = 90 }),
Vision.Keyframe(Ease, 3, { BackgroundColor3 = Red }),
Colour eases from white to red over the full two seconds while rotation does its own thing at the halfway mark. A property with only one keyframe is simply applied when that time arrives.
A track holds its value before its first keyframe and after its last, and lands on the authored value exactly rather than wherever the final frame happened to fall.
#Events
The fourth argument maps an event name to its arguments.
Vision.Keyframe(Ease, 0, { Rotation = 0 }, { Step = { "left", 3 } })
Run:Bind("Step", function(Object, Side, Count)
print(Object.Name, Side, Count)
end)
The instance is always the first argument, as everywhere else in Vision.
Events at time zero fire on the first frame, after Animate returns, so
there is always time to bind to them. Bind returns a handle with
Disconnect.
#Playback
Run.Speed = 2 -- twice as fast
Run.Speed = 0.5 -- half
Run.Speed = 0 -- paused, still alive
Run.Looped = true -- wraps at the end and fires its events again
Run.SmoothLoop = true -- eases across the seam instead of cutting
Speed is a multiplier on elapsed time and cannot be negative. Looped can
be turned off mid-run, and the animation then finishes at the end of the
current pass.
#SmoothLoop
A loop normally cuts: at the end of a pass every property snaps back to its first keyframe. That is right for a cycle that already ends where it began, and jarring for one that does not.
With SmoothLoop on, the animation instead restages. Time holds at the
start while every property travels from where it ended back to its first
keyframe, and the next pass begins once they have all arrived.
Run.Looped = true
Run.SmoothLoop = true
Run.RestageTime = 0.4
RestageTime is how long that walk back takes, in seconds, and defaults to
0.25. Setting it to 0 turns the restage back into a hard cut.
Each property restages on its own last keyframe's easing, so a property
that ends on a Sine tween eases back, and one that ends on a spring springs
back - overshooting the start and settling, rather than sliding flatly into
it. A sprung property is allowed to finish settling even if that outlasts
RestageTime.
SmoothLoop does nothing unless Looped is on.
#Playing
Run:Play()
Starts from wherever the playhead is, so it resumes a stopped animation rather than restarting it. Calling it on one that is already running does nothing. The pose at that point is applied immediately.
Run:Stop() -- freezes half way
Run:Play() -- carries on from half way
Run:Jump(0) -- rewind
Run:Play() -- from the top
Run.Time is where the playhead sits, in seconds, and survives a Stop.
#Jumping
Run:Jump(Time: number)
Moves the playhead to that point in seconds and applies the pose there
immediately, without waiting a frame. The time is clamped to the length of the
timeline, so Jump(0) rewinds and a time past the end parks it on the last
keyframe.
Run.Speed = 0
Run:Jump(1.5) -- scrub to a moment and hold there
Events between where you were and where you land do not fire - a jump is a seek, not a fast forward. Events after the new position still fire as the animation reaches them, and jumping backwards re-arms the ones you passed.
Jumping cancels a restage in progress, and a sprung property restarts its spring from the pose at the new time, since a spring has no meaningful state part way through a seek.
Jumping a stopped animation moves its playhead without touching anything, so
the pose lands when you Play. Jumping a destroyed animation does nothing.
#Stopping
| member | behaviour |
|---|---|
Run:Play() |
start, or resume from the playhead |
Run:Stop() |
freezes every property where it stands |
Run:Destroy() |
stops it and drops every listener |
Run:Jump(Time) |
seek to a point in seconds |
Run:Await() |
yields until it finishes or is stopped |
Run.Time |
where the playhead sits, in seconds |
Run.Finished |
true when it is not playing |
An animation is owned by the runtime while it plays, so you can drop the handle and let it run. Keep it if you want to stop it, retime it, or bind to its events.
After Destroy nothing holds the animation and nothing holds the functions
you bound to it.
#Sharing properties
An animation claims each property or value it drives, the same way a spring does. Starting a spring on a property an animation is driving cancels the animation, and starting an animation cancels the springs it overlaps. Last call wins.
Destroying the instance stops the animation on the spot.