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".

StyleCurve
LinearNo easing — pure lerp.
SineHalf a sine wave. Gentle.
Quad / Cubic / Quart / QuintPolynomial. Higher = sharper.
ExpoExponential. Very sudden.
CircQuarter-circle.
BackOvershoots before settling.
ElasticDamped oscillation around the goal.
BounceSettles like a ball hitting the floor.
DirectionEffect
InEases in. Slow start, fast end.
Out (default)Eases out. Fast start, slow end.
InOutEases 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

TweenService.new(target, duration, style, direction?, properties) -> Tween

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 a CFrame
    • Color — goal is a Color3
    • Size — goal is a Vector
  • DistortionBox
    • CFrame — goal is a CFrame
    • Size — goal is a Vector
  • GUI primitive (Square, Image, Font, etc.)
    • Position — goal is a Dim
    • Size — goal is a Dim
    • Color — goal is a Color3
    • Transparency — goal is a number (0..1)
    • ZIndex — goal is a number (rounded each tick)
    • Rotation — goal is a number (degrees)
  • SFX Sound
    • Volume — goal is a number
    • Pitch / Speed — goal is a number
    • Pan — goal is a number (-1..1)
    • Distortion — goal is a number
    • Position — goal is a Vector
    • MinFalloff — goal is a number
    • MaxFalloff — goal is a number
  • VoiceChannel
    • Volume — goal is a number
    • Pitch / Speed — goal is a number
    • Position — goal is a Vector
    • MinFalloff — goal is a number
    • MaxFalloff — goal is a number
  • Movable
    • CFrame — goal is a CFrame (3D mode)
    • Position — goal is a Dim (2D mode)

    Children are propagated by the delta on every tick.

  • Sizable
    • Size — goal is a Vector (3D mode) or Dim (2D mode)

    Children scale by the ratio each tick.

  • Billboard
    • Position — goal is a Vector (world-space anchor)
    • Size — goal is a Dim (pixel canvas)
  • SoundByte Modifier
    • Value — goal is a number
    • Min — goal is a number
    • Max — goal is a number

    Live-DSP modifier kinds (Volume, Pan, Distortion, Tremolo, Vibrato, BitCrusher, NoiseGate, Compressor, Limiter, Saturation, RingMod, Chorus, Flanger, StereoWiden, Wobble, Telephone, Underwater, Mute) pick up Value changes 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 a number
    • FalloffMinDistance — goal is a number
    • FalloffMaxDistance — goal is a number
    • Position — goal is a Vector
    • CFrame — goal is a CFrame

    Tweening Position or CFrame auto-enables 3D spatial mode on the output. Tweening Volume flows 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.

TweenService.Create(target, duration, style, direction?, properties) -> Tween

Alias for new (Roblox-style spelling).

TweenService.Lerp(start, goal, alpha, style?, direction?) -> same type as start / goal

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

.CompletedSignal

Fires when the tween reaches its end. Does not fire on :Cancel(). Fires inside the heart loop, on the main Lua thread.

.Statestringread-only

One of "Idle", "Playing", "Paused", "Completed", or "Cancelled".

.Elapsednumberread-only

Seconds elapsed in the current play cycle. Resets when :Play() is called on a Completed / Cancelled / Idle tween.

.Durationnumberread-only

The duration the tween was created with, in seconds.

:Play()method

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.

:Pause()method

Stop advancing time but keep the tween registered.

:Resume()method

Continue from the same elapsed time after :Pause().

:Cancel()method

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.

:Wait()method

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.