Skip to content
Engine

Math and Space

Spring

Physically based springs for numbers, Vector2, Vector3 and CFrame. Natural motion for cameras, UI and effects without tweens.

PackageShared

Getting it

local Spring = Engine:GetPackage("Spring")

Overview

A spring moves its Position towards its Target like a damped spring would: it accelerates, slows down near the target and, depending on the damper, may overshoot and wobble. Change the target at any time and the spring reacts smoothly from wherever it is, keeping its current velocity. That makes springs ideal for things that follow input, such as cameras, cursors, hover effects or a health bar.

Springs are evaluated analytically from the time since the last change. There is no per-frame update to call; reading Position at any moment gives the exact value.

A UI panel that springs open
local Spring = Engine:GetPackage("Spring")

local scale = Spring.new(0, { speed = 18, damper = 0.6 })
local unbind = scale:Bind(panel.UIScale, "Scale")

openButton.Activated:Connect(function()
	scale.Target = 1
end)

closeButton.Activated:Connect(function()
	scale.Target = 0
end)

Speed and damper

SettingEffect
speedHow fast the spring moves. Higher is snappier. Must be positive.
damperHow quickly motion dies out. 1 reaches the target without overshooting (critically damped). Below 1 it overshoots and bounces; 0 never stops. Above 1 it creeps in slowly.

Constructors

new

Spring.new(initial: SpringValue, config: { speed: number?, damper: number? }?, clock: (() -> number)?): Spring

A spring at rest at initial. The type of initial (number, Vector2, Vector3 or CFrame) fixes the spring's Kind. Speed and damper default to the configuration values.

Presets

Spring.FromPreset(name: string, initial: SpringValue, clock?): Spring
Spring.Gentle(initial), Spring.Wobbly(initial), Spring.Stiff(initial), Spring.Slow(initial), Spring.Bouncy(initial)

Ready-made speed and damper pairs. Their values come from the configuration.

FunctionDescription
Spring.RegisterPreset(name, speed, damper)Adds your own preset, which also becomes a shortcut such as Spring.Snappy(initial).
Spring.GetPresetInfo(name): (speed, damper)Reads a preset.
Spring.GetPresetNames(): { string }Every preset name.
Spring.IsSpringValue(value): booleanWhether a value can be sprung.

Spring members

MemberDescription
TargetWhere the spring is heading. Set it to move the spring.
PositionRead-only. The current value.
VelocityRead-only. The current rate of change (a Vector3 of linear velocity for CFrame springs).
Speed, DamperCan be changed at any time.
KindRead-only: "number", "Vector2", "Vector3" or "CFrame".
Settled: SignalFires when the spring comes to rest at its target. Rescheduled whenever the spring changes.

Impulse

spring:Impulse(velocity: SpringValue)

Adds velocity, like hitting the spring. For CFrame springs pass a Vector3 (linear velocity only).

-- Camera shake on landing
cameraOffset:Impulse(Vector3.new(0, -12, 0))

SkipToTarget / Stop

spring:SkipToTarget()
spring:Stop()

SkipToTarget jumps straight to the target and stops. Stop freezes the spring where it is right now by making its current position the new target.

Bind

spring:Bind(instance: Instance, property: string): () -> ()

Writes Position to instance[property] every frame. Returns a function that stops the binding; it also stops when the instance is destroyed.

Extrapolate

spring:Extrapolate(seconds: number): SpringValue

Where the spring will be after seconds if nothing changes.

IsSettled / TimeToSettle

spring:IsSettled(threshold: number?): boolean
spring:TimeToSettle(threshold: number?): number

Whether the distance to the target is below threshold, and how many seconds until it will be.

Clone

spring:Clone(): Spring

A new spring with the same state that moves on independently.

Spring groups

Spring.Group(initial: { [string]: SpringValue }, config: { [string]: SpringConfig }?, clock?): SpringGroup

Several named springs that you drive together, for example position, size and transparency of one frame.

local card = Spring.Group({
	Position = UDim2.fromScale(0.5, 0.6).Y.Scale,
	Transparency = 1,
})

card:SetTarget({ Position = 0.5, Transparency = 0 })
MethodDescription
:Get(name): SpringOne spring of the group.
:Names(): { string }Every spring name.
:SetTarget(targets)Set several targets at once.
:Impulse(impulses)Impulse several springs.
:SkipToTarget(names?), :Stop(names?)For all springs, or only the named ones.
:IsSettled(threshold?), :TimeToSettle(threshold?)For the whole group.
:Bind(instance, propertyMap)Bind springs to properties: { [springName] = propertyName }. Returns an unbind function.
:Extrapolate(seconds)Future value of every spring.
:Clone()Independent copy of the group.

Configuration

Defaults from DefaultConfiguration/Packages/Spring15 settings
SettingDefaultWhat it does
DefaultSpeed10Speed Spring.new() uses when config.speed is omitted; settles in a few tenths of a second.
DefaultDamper1Damper Spring.new() uses when config.damper is omitted; 1 is critically damped (no overshoot).
DefaultSettleThreshold0.01Position error under which :IsSettled()/:TimeToSettle() count the spring as settled.
SettleSearchSamples200Samples :TimeToSettle() steps through while searching.
SettleSearchMaxSeconds30Furthest time :TimeToSettle() searches before giving up.
PresetGentleSpeed6Speed of the Spring.Gentle preset.
PresetGentleDamper1Damper of the Spring.Gentle preset.
PresetWobblySpeed12Speed of the Spring.Wobbly preset.
PresetWobblyDamper0.3Damper of the Spring.Wobbly preset.
PresetStiffSpeed20Speed of the Spring.Stiff preset.
PresetStiffDamper1Damper of the Spring.Stiff preset.
PresetSlowSpeed2Speed of the Spring.Slow preset.
PresetSlowDamper1Damper of the Spring.Slow preset.
PresetBouncySpeed10Speed of the Spring.Bouncy preset.
PresetBouncyDamper0.15Damper of the Spring.Bouncy preset.

Types

export type SpringValue = number | Vector2 | Vector3 | CFrame
export type SpringConfig = { speed: number?, damper: number? }
export type Spring
export type SpringGroup