TweenService
Rust-side interpolation engine. Tweens are created once from Lua then
stepped entirely on the Rust heart loop, so the per-frame
interpolation never crosses the Lua VM. The VM only re-enters when a
tween's Completed signal fires (once per tween, not per
frame).
local TweenService = import("TweenService")
local Primitives = import("Primitives")
local CFrame, Vector, Color3 = Primitives.CFrame, Primitives.Vector, Primitives.Color3
local tw = TweenService.new(part, 1.5, "Bounce", "Out", {
CFrame = CFrame.new(Vector.new(0, 10, 0), Vector.new(0, 0, 0)),
Color = Color3.fromRGB(255, 80, 80),
})
tw:Play()
tw.Completed:Connect(function() print("done") end)
-- Pure math, no tween object:
local v = TweenService.Lerp(0, 100, 0.5, "Bounce", "Out")
Easing styles and directions
Style and direction are separate strings. You don't
concatenate them. Direction is optional and defaults to
"Out".
| Style | Curve |
|---|---|
Linear | No easing — pure lerp. |
Sine | Half a sine wave. Gentle. |
Quad / Cubic / Quart / Quint | Polynomial. Higher = sharper. |
Expo | Exponential. Very sudden. |
Circ | Quarter-circle. |
Back | Overshoots before settling. |
Elastic | Damped oscillation around the goal. |
Bounce | Settles like a ball hitting the floor. |
| Direction | Effect |
|---|---|
In | Eases in. Slow start, fast end. |
Out (default) | Eases out. Fast start, slow end. |
InOut | Eases at both ends. Slow start, slow end, fast middle. |
All values are case-insensitive. You can also pass nil for
either and get the defaults ("Linear" /
"Out").
TweenService API
Create a tween. target is a tweenable userdata.
duration is in seconds. properties is
a table of { propertyName = goalValue }.
Supported targets and properties:
- BasePart
CFrame— goal is aCFrameColor— goal is aColor3Size— goal is aVector
- DistortionBox
CFrame— goal is aCFrameSize— goal is aVector
- GUI primitive (Square, Image, Font, etc.)
Position— goal is aDimSize— goal is aDimColor— goal is aColor3Transparency— goal is anumber(0..1)ZIndex— goal is anumber(rounded each tick)Rotation— goal is anumber(degrees)
- SFX Sound
Volume— goal is anumberPitch/Speed— goal is anumberPan— goal is anumber(-1..1)Distortion— goal is anumberPosition— goal is aVectorMinFalloff— goal is anumberMaxFalloff— goal is anumber
- VoiceChannel
Volume— goal is anumberPitch/Speed— goal is anumberPosition— goal is aVectorMinFalloff— goal is anumberMaxFalloff— goal is anumber
- Movable
CFrame— goal is aCFrame(3D mode)Position— goal is aDim(2D mode)
Children are propagated by the delta on every tick.
- Sizable
Size— goal is aVector(3D mode) orDim(2D mode)
Children scale by the ratio each tick.
- Billboard
Position— goal is aVector(world-space anchor)Size— goal is aDim(pixel canvas)
- SoundByte Modifier
Value— goal is anumberMin— goal is anumberMax— goal is anumber
Live-DSP modifier kinds (Volume, Pan, Distortion, Tremolo, Vibrato, BitCrusher, NoiseGate, Compressor, Limiter, Saturation, RingMod, Chorus, Flanger, StereoWiden, Wobble, Telephone, Underwater, Mute) pick up
Valuechanges per-sample, so a tween while playing actually morphs the sound. Snapshot kinds (Speed, LowPass, HighPass, BandPass, Echo, Reverb, FadeIn, FadeOut, Delay) capture the value at:Play()time — the tween still completes cleanly, but you have to re-Play to hear it. - SoundByte OutputNode
Volume— goal is anumberFalloffMinDistance— goal is anumberFalloffMaxDistance— goal is anumberPosition— goal is aVectorCFrame— goal is aCFrame
Tweening
PositionorCFrameauto-enables 3D spatial mode on the output. TweeningVolumeflows straight into the rodio Sink, so it ducks the live signal in real time without rebuilding the source chain.
The returned Tween starts in "Idle". Call
:Play() to begin. Start values are
snapshotted at :Play(), not at
construction time, so you can build the tween early and play
it later from wherever the target is then. Re-playing after
:Cancel() or completion also re-snapshots, so
you can chain easily.
direction is optional and may be omitted —
the call (target, duration, style, properties)
also works.
Alias for new (Roblox-style spelling).
Single-shot eased interpolation. Same families and directions
as new, but applied to a one-off triple and
returned. No Tween object is created. Supported value types:
number, Vector, Color3,
CFrame, Dim. Both start and goal
must be the same type.
Tween
Fires when the tween reaches its end. Does not fire
on :Cancel(). Fires inside the heart loop, on
the main Lua thread.
One of "Idle", "Playing",
"Paused", "Completed", or
"Cancelled".
Seconds elapsed in the current play cycle. Resets when
:Play() is called on a Completed / Cancelled /
Idle tween.
The duration the tween was created with, in seconds.
Begin playing. If the tween was previously Completed or Cancelled, this re-snapshots the target's current property values as the new start state and runs the easing again. Calling on a Playing tween is a no-op.
Stop advancing time but keep the tween registered.
Continue from the same elapsed time after :Pause().
Mark the tween as Cancelled. The target's properties stay
wherever they were when Cancel was called (no snap to start
or end). Completed does NOT fire.
:Play() afterward re-runs from the current state.
Yield the current coroutine until the tween's
Completed signal fires. Returns immediately if
already Completed or Cancelled. Must be called from a
coroutine.
Why is it Rust-side?
Tweening one value per frame is cheap. Tweening a thousand objects per
frame through the Lua VM is not. TweenService stores all the active
tweens as Rust structs and steps them on the same heart loop that
drives physics. Each tick we lock the target's
Arc<Mutex<PartState>> directly and write the
interpolated CFrame / Color / Size,
no Lua call needed.
The VM re-enters exactly once per tween, when Completed
fires. Everything in between — including the eased math —
is pure Rust.