--[==[
BoatTween (because TweenService2 was taken)
by boatbomber (Zack Ovits)
© 2020
API:
function BoatTween:Create(Object,Data)
returns a Tween object
Params:
- Object
The instance that is having it's properties tweened
- Data
A dictionary of the various settings of the tween
{
number Time = Any positive number
How long the tween should take to complete
string EasingStyle = Any TweenStyle from the list below
The style that the tween follows
(Note: Uses strings instead of Enum.EasingStyle to allow us to add styles that Roblox doesn't support)
List of available styles:
Linear Quad Cubic
Quart Quint Sine
Expo Circ Elastic
Back Bounce Smooth
Smoother RidiculousWiggle RevBack
Spring SoftSpring Standard
Sharp Acceleration Deceleration
StandardProductive EntranceProductive ExitProductive
StandardExpressive EntranceExpressive ExitExpressive
FabricStandard FabricAccelerate FabricDecelerate
UWPAccelerate MozillaCurve
string EasingDirection = "In" or "Out" or "InOut" or "OutIn"
The direction for the TweenStyle to adhere to
number DelayTime = 0 -> math.huge
The amount of time before the tween begins playback after calling :Play() on the tween
(Note: doesn't affect :Resume() calls)
number RepeatCount = -1 -> math.huge
How many times the tween should repeat with -1 being infinity
(Note: will wait the DelayTime in between replaying)
boolean Reverses = false or true
Whether the tween should reverse itself after completion
(note: Waits the DelayTime before reversing)
table Goal = dictionary
A dictionary where the keys are properties to tween and the values are the end goals of said properties
You may tween any property with value of the following types:
number boolean CFrame
Color3 UDim2 UDim
Ray NumberRange NumberSequenceKeypoint
PhysicalProperties NumberSequence Region3
Rect Vector2 Vector3
string StepType = "Stepped" or "Heartbeat" or "RenderStepped"
The event of RunService for the tween to move on
}
function Tween:Play()
Plays the tween, starting from the beginning
function Tween:Stop()
Stops the tween, freezing it in its current state
function Tween:Resume()
Plays the tween, starting from current position and time
function TweenObject:Destroy()
Clears connections, stops playback, destroys objects
property Tween.Instance
The object being tweened
property Tween.PlaybackState
An Enum.PlaybackState representing the Tween's current state
event Tween.Stopped
Fired when a Tween ends from the :Stop() function
event Tween.Completed
Fired when a Tween ends due to :Play() being completed
event Tween.Resumed
Fired when a Tween is played through the :Resume() function
--]==]
local function Linear(T)
return T
end
local function Bezier(X1, Y1, X2, Y2)
if not (X1 and Y1 and X2 and Y2) then
error("Need 4 numbers to construct a Bezier curve", 0)
end
if not (0 <= X1 and X1 <= 1 and 0 <= X2 and X2 <= 1) then
error("The x values must be within range [0, 1]", 0)
end
if X1 == Y1 and X2 == Y2 then
return Linear
end
local SampleValues = {}
for Index = 0, 10 do
local IndexDiv10 = Index / 10
SampleValues[Index] = (((1 - 3 * X2 + 3 * X2) * IndexDiv10 + (3 * X2 - 6 * X1)) * IndexDiv10 + (3 * X1)) * IndexDiv10
end
return function(T)
if X1 == Y1 and X2 == Y2 then
return Linear
end
if T == 0 or T == 1 then
return T
end
local GuessT
local IntervalStart = 0
local CurrentSample = 1
while CurrentSample ~= 10 and SampleValues[CurrentSample] <= T do
IntervalStart += 0.1
CurrentSample += 1
end
CurrentSample -= 1
local Dist = (T - SampleValues[CurrentSample]) / (SampleValues[CurrentSample + 1] - SampleValues[CurrentSample])
local GuessForT = IntervalStart + Dist / 10
local InitialSlope = 3 * (1 - 3 * X2 + 3 * X1) * GuessForT * GuessForT + 2 * (3 * X2 - 6 * X1) * GuessForT + (3 * X1)
if InitialSlope >= 0.001 then
for _ = 0, 3 do
local CurrentSlope = 3 * (1 - 3 * X2 + 3 * X1) * GuessForT * GuessForT + 2 * (3 * X2 - 6 * X1) * GuessForT + (3 * X1)
local CurrentX = ((((1 - 3 * X2 + 3 * X1) * GuessForT + (3 * X2 - 6 * X1)) * GuessForT + (3 * X1)) * GuessForT) - T
GuessForT -= CurrentX / CurrentSlope
end
GuessT = GuessForT
elseif InitialSlope == 0 then
GuessT = GuessForT
else
local AB = IntervalStart + 0.1
local CurrentX, CurrentT, Index = 0, nil, nil
while math.abs(CurrentX) > 0.0000001 and Index < 10 do
CurrentT = IntervalStart + (AB - IntervalStart) / 2
CurrentX = ((((1 - 3 * X2 + 3 * X1) * CurrentT + (3 * X2 - 6 * X1)) * CurrentT + (3 * X1)) * CurrentT) - T
if CurrentX > 0 then
AB = CurrentT
else
IntervalStart = CurrentT
end
Index += 1
end
GuessT = CurrentT
end
return (((1 - 3 * Y2 + 3 * Y1) * GuessT + (3 * Y2 - 6 * Y1)) * GuessT + (3 * Y1)) * GuessT
end
end
local ipairs = ipairs
local BLACK_COLOR3 = Color3.new()
-- Generic Roblox DataType lerp function.
local function RobloxLerp(V0, V1)
return function(Alpha)
return V0:Lerp(V1, Alpha)
end
end
local function Lerp(Start, Finish, Alpha)
return Start + Alpha * (Finish - Start)
end
local function SortByTime(A, B)
return A.Time < B.Time
end
local function Color3Lerp(C0, C1)
local L0, U0, V0
local R0, G0, B0 = C0.R, C0.G, C0.B
R0 = R0 < 0.0404482362771076 and R0 / 12.92 or 0.87941546140213 * (R0 + 0.055) ^ 2.4
G0 = G0 < 0.0404482362771076 and G0 / 12.92 or 0.87941546140213 * (G0 + 0.055) ^ 2.4
B0 = B0 < 0.0404482362771076 and B0 / 12.92 or 0.87941546140213 * (B0 + 0.055) ^ 2.4
local Y0 = 0.2125862307855956 * R0 + 0.71517030370341085 * G0 + 0.0722004986433362 * B0
local Z0 = 3.6590806972265883 * R0 + 11.4426895800574232 * G0 + 4.1149915024264843 * B0
local _L0 = Y0 > 0.008856451679035631 and 116 * Y0 ^ (1 / 3) - 16 or 903.296296296296 * Y0
if Z0 > 1E-15 then
local X = 0.9257063972951867 * R0 - 0.8333736323779866 * G0 - 0.09209820666085898 * B0
L0, U0, V0 = _L0, _L0 * X / Z0, _L0 * (9 * Y0 / Z0 - 0.46832)
else
L0, U0, V0 = _L0, -0.19783 * _L0, -0.46832 * _L0
end
local L1, U1, V1
local R1, G1, B1 = C1.R, C1.G, C1.B
R1 = R1 < 0.0404482362771076 and R1 / 12.92 or 0.87941546140213 * (R1 + 0.055) ^ 2.4
G1 = G1 < 0.0404482362771076 and G1 / 12.92 or 0.87941546140213 * (G1 + 0.055) ^ 2.4
B1 = B1 < 0.0404482362771076 and B1 / 12.92 or 0.87941546140213 * (B1 + 0.055) ^ 2.4
local Y1 = 0.2125862307855956 * R1 + 0.71517030370341085 * G1 + 0.0722004986433362 * B1
local Z1 = 3.6590806972265883 * R1 + 11.4426895800574232 * G1 + 4.1149915024264843 * B1
local _L1 = Y1 > 0.008856451679035631 and 116 * Y1 ^ (1 / 3) - 16 or 903.296296296296 * Y1
if Z1 > 1E-15 then
local X = 0.9257063972951867 * R1 - 0.8333736323779866 * G1 - 0.09209820666085898 * B1
L1, U1, V1 = _L1, _L1 * X / Z1, _L1 * (9 * Y1 / Z1 - 0.46832)
else
L1, U1, V1 = _L1, -0.19783 * _L1, -0.46832 * _L1
end
return function(Alpha)
local L = (1 - Alpha) * L0 + Alpha * L1
if L < 0.0197955 then
return BLACK_COLOR3
end
local U = ((1 - Alpha) * U0 + Alpha * U1) / L + 0.19783
local V = ((1 - Alpha) * V0 + Alpha * V1) / L + 0.46832
local Y = (L + 16) / 116
Y = Y > 0.206896551724137931 and Y * Y * Y or 0.12841854934601665 * Y - 0.01771290335807126
local X = Y * U / V
local Z = Y * ((3 - 0.75 * U) / V - 5)
local R = 7.2914074 * X - 1.5372080 * Y - 0.4986286 * Z
local G = -2.1800940 * X + 1.8757561 * Y + 0.0415175 * Z
local B = 0.1253477 * X - 0.2040211 * Y + 1.0569959 * Z
if R < 0 and R < G and R < B then
R, G, B = 0, G - R, B - R
elseif G < 0 and G < B then
R, G, B = R - G, 0, B - G
elseif B < 0 then
R, G, B = R - B, G - B, 0
end
R = R < 3.1306684425E-3 and 12.92 * R or 1.055 * R ^ (1 / 2.4) - 0.055 -- 3.1306684425E-3
G = G < 3.1306684425E-3 and 12.92 * G or 1.055 * G ^ (1 / 2.4) - 0.055
B = B < 3.1306684425E-3 and 12.92 * B or 1.055 * B ^ (1 / 2.4) - 0.055
R = R > 1 and 1 or R < 0 and 0 or R
G = G > 1 and 1 or G < 0 and 0 or G
B = B > 1 and 1 or B < 0 and 0 or B
return Color3.new(R, G, B)
end
end
local Lerps = setmetatable({
boolean = function(V0, V1)
return function(Alpha)
if Alpha < 0.5 then
return V0
else
return V1
end
end
end;
number = function(V0, V1)
local Delta = V1 - V0
return function(Alpha)
return V0 + Delta * Alpha
end
end;
string = function(V0, V1)
local RegularString = false
local N0, D do
local Sign0, H0, M0, S0 = string.match(V0, "^([+-]?)(%d*):[+-]?(%d*):[+-]?(%d*)$")
local Sign1, H1, M1, S1 = string.match(V1, "^([+-]?)(%d*):[+-]?(%d*):[+-]?(%d*)$")
if Sign0 and Sign1 then
N0 = 3600 * (tonumber(H0) or 0) + 60 * (tonumber(M0) or 0) + (tonumber(S0) or 0)
local N1 = 3600 * (tonumber(H1) or 0) + 60 * (tonumber(M1) or 0) + (tonumber(S1) or 0)
if Sign0 == "-" then
N0 = -N0
end
D = (43200 + (Sign1 ~= "-" and N1 or -N1) - N0) % 86400 - 43200
else
RegularString = true
end
end
if RegularString then
local Length = #V1
return function(Alpha)
Alpha = 1 + Length * Alpha
return string.sub(V1, 1, Alpha < Length and Alpha or Length)
end
else
return function(Alpha)
local FS = (N0 + D * Alpha) % 86400
local S = math.abs(FS)
return string.format(
FS < 0 and "-%.2u:%.2u:%.2u" or "%.2u:%.2u:%.2u",
(S - S % 3600) / 3600,
(S % 3600 - S % 60) / 60,
S % 60
)
end
end
end;
CFrame = RobloxLerp;
Color3 = Color3Lerp;
NumberRange = function(V0, V1)
local Min0, Max0 = V0.Min, V0.Max
local DeltaMin, DeltaMax = V1.Min - Min0, V1.Max - Max0
return function(Alpha)
return NumberRange.new(Min0 + Alpha * DeltaMin, Max0 + Alpha * DeltaMax)
end
end;
NumberSequenceKeypoint = function(V0, V1)
local T0, Value0, E0 = V0.Time, V0.Value, V0.Envelope
local DT, DV, DE = V1.Time - T0, V1.Value - Value0, V1.Envelope - E0
return function(Alpha)
return NumberSequenceKeypoint.new(T0 + Alpha * DT, Value0 + Alpha * DV, E0 + Alpha * DE)
end
end;
PhysicalProperties = function(V0, V1)
local D0, E0, EW0, F0, FW0 =
V0.Density, V0.Elasticity,
V0.ElasticityWeight, V0.Friction,
V0.FrictionWeight
local DD, DE, DEW, DF, DFW =
V1.Density - D0, V1.Elasticity - E0,
V1.ElasticityWeight - EW0, V1.Friction - F0,
V1.FrictionWeight - FW0
return function(Alpha)
return PhysicalProperties.new(
D0 + Alpha * DD,
E0 + Alpha * DE, EW0 + Alpha * DEW,
F0 + Alpha * DF, FW0 + Alpha * DFW
)
end
end;
Ray = function(V0, V1)
local O0, D0, O1, D1 = V0.Origin, V0.Direction, V1.Origin, V1.Direction
local OX0, OY0, OZ0, DX0, DY0, DZ0 = O0.X, O0.Y, O0.Z, D0.X, D0.Y, D0.Z
local DOX, DOY, DOZ, DDX, DDY, DDZ = O1.X - OX0, O1.Y - OY0, O1.Z - OZ0, D1.X - DX0, D1.Y - DY0, D1.Z - DZ0
return function(Alpha)
return Ray.new(
Vector3.new(OX0 + Alpha * DOX, OY0 + Alpha * DOY, OZ0 + Alpha * DOZ),
Vector3.new(DX0 + Alpha * DDX, DY0 + Alpha * DDY, DZ0 + Alpha * DDZ)
)
end
end;
UDim = function(V0, V1)
local SC, OF = V0.Scale, V0.Offset
local DSC, DOF = V1.Scale - SC, V1.Offset - OF
return function(Alpha)
return UDim.new(SC + Alpha * DSC, OF + Alpha * DOF)
end
end;
UDim2 = RobloxLerp;
Vector2 = RobloxLerp;
Vector3 = RobloxLerp;
Rect = function(V0, V1)
return function(Alpha)
return Rect.new(
V0.Min.X + Alpha * (V1.Min.X - V0.Min.X), V0.Min.Y + Alpha * (V1.Min.Y - V0.Min.Y),
V0.Max.X + Alpha * (V1.Max.X - V0.Max.X), V0.Max.Y + Alpha * (V1.Max.Y - V0.Max.Y)
)
end
end;
Region3 = function(V0, V1)
return function(Alpha)
local imin = Lerp(V0.CFrame * (-V0.Size / 2), V1.CFrame * (-V1.Size / 2), Alpha)
local imax = Lerp(V0.CFrame * (V0.Size / 2), V1.CFrame * (V1.Size / 2), Alpha)
local iminx = imin.X
local imaxx = imax.X
local iminy = imin.Y
local imaxy = imax.Y
local iminz = imin.Z
local imaxz = imax.Z
return Region3.new(
Vector3.new(iminx < imaxx and iminx or imaxx, iminy < imaxy and iminy or imaxy, iminz < imaxz and iminz or imaxz),
Vector3.new(iminx > imaxx and iminx or imaxx, iminy > imaxy and iminy or imaxy, iminz > imaxz and iminz or imaxz)
)
end
end;
NumberSequence = function(V0, V1)
return function(Alpha)
local keypoints = {}
local addedTimes = {}
local keylength = 0
for _, ap in ipairs(V0.Keypoints) do
local closestAbove, closestBelow
for _, bp in ipairs(V1.Keypoints) do
if bp.Time == ap.Time then
closestAbove, closestBelow = bp, bp
break
elseif bp.Time < ap.Time and (closestBelow == nil or bp.Time > closestBelow.Time) then
closestBelow = bp
elseif bp.Time > ap.Time and (closestAbove == nil or bp.Time < closestAbove.Time) then
closestAbove = bp
end
end
local bValue, bEnvelope
if closestAbove == closestBelow then
bValue, bEnvelope = closestAbove.Value, closestAbove.Envelope
else
local p = (ap.Time - closestBelow.Time) / (closestAbove.Time - closestBelow.Time)
bValue = (closestAbove.Value - closestBelow.Value) * p + closestBelow.Value
bEnvelope = (closestAbove.Envelope - closestBelow.Envelope) * p + closestBelow.Envelope
end
keylength += 1
keypoints[keylength] = NumberSequenceKeypoint.new(ap.Time, (bValue - ap.Value) * Alpha + ap.Value, (bEnvelope - ap.Envelope) * Alpha + ap.Envelope)
addedTimes[ap.Time] = true
end
for _, bp in ipairs(V1.Keypoints) do
if not addedTimes[bp.Time] then
local closestAbove, closestBelow
for _, ap in ipairs(V0.Keypoints) do
if ap.Time == bp.Time then
closestAbove, closestBelow = ap, ap
break
elseif ap.Time < bp.Time and (closestBelow == nil or ap.Time > closestBelow.Time) then
closestBelow = ap
elseif ap.Time > bp.Time and (closestAbove == nil or ap.Time < closestAbove.Time) then
closestAbove = ap
end
end
local aValue, aEnvelope
if closestAbove == closestBelow then
aValue, aEnvelope = closestAbove.Value, closestAbove.Envelope
else
local p = (bp.Time - closestBelow.Time) / (closestAbove.Time - closestBelow.Time)
aValue = (closestAbove.Value - closestBelow.Value) * p + closestBelow.Value
aEnvelope = (closestAbove.Envelope - closestBelow.Envelope) * p + closestBelow.Envelope
end
keylength += 1
keypoints[keylength] = NumberSequenceKeypoint.new(bp.Time, (bp.Value - aValue) * Alpha + aValue, (bp.Envelope - aEnvelope) * Alpha + aEnvelope)
end
end
table.sort(keypoints, SortByTime)
return NumberSequence.new(keypoints)
end
end;
ColorSequence = function(V0, V1)
return function(Alpha)
local keypoints = {}
local addedTimes = {}
local keylength = 0
for _, ap in ipairs(V0.Keypoints) do
local closestAbove, closestBelow
for _, bp in ipairs(V1.Keypoints) do
if bp.Time == ap.Time then
closestAbove, closestBelow = bp, bp
break
elseif bp.Time < ap.Time and (closestBelow == nil or bp.Time > closestBelow.Time) then
closestBelow = bp
elseif bp.Time > ap.Time and (closestAbove == nil or bp.Time < closestAbove.Time) then
closestAbove = bp
end
end
local bValue
if closestAbove == closestBelow then
bValue = closestAbove.Value
else
bValue = Color3Lerp(closestBelow.Value, closestAbove.Value)((ap.Time - closestBelow.Time) / (closestAbove.Time - closestBelow.Time))
end
keylength += 1
keypoints[keylength] = ColorSequenceKeypoint.new(ap.Time, Color3Lerp(ap.Value, bValue)(Alpha))
addedTimes[ap.Time] = true
end
for _, bp in ipairs(V1.Keypoints) do
if not addedTimes[bp.Time] then
local closestAbove, closestBelow
for _, ap in ipairs(V0.Keypoints) do
if ap.Time == bp.Time then
closestAbove, closestBelow = ap, ap
break
elseif ap.Time < bp.Time and (closestBelow == nil or ap.Time > closestBelow.Time) then
closestBelow = ap
elseif ap.Time > bp.Time and (closestAbove == nil or ap.Time < closestAbove.Time) then
closestAbove = ap
end
end
local aValue
if closestAbove == closestBelow then
aValue = closestAbove.Value
else
aValue = Color3Lerp(closestBelow.Value, closestAbove.Value)((bp.Time - closestBelow.Time) / (closestAbove.Time - closestBelow.Time))
end
keylength += 1
keypoints[keylength] = ColorSequenceKeypoint.new(bp.Time, Color3Lerp(bp.Value, aValue)(Alpha))
end
end
table.sort(keypoints, SortByTime)
return ColorSequence.new(keypoints)
end
end;
}, {
__index = function(_, Index)
error("No lerp function is defined for type " .. tostring(Index) .. ".", 4)
end;
__newindex = function(_, Index)
error("No lerp function is defined for type " .. tostring(Index) .. ".", 4)
end;
})
local function RevBack(T)
T = 1 - T
return 1 - (math.sin(T * 1.5707963267949) + (math.sin(T * 3.1415926535898) * (math.cos(T * 3.1415926535898) + 1) / 2))
end
local function Linear(T)
return T
end
-- @specs https://material.io/guidelines/motion/duration-easing.html#duration-easing-natural-easing-curves
local Sharp = Bezier(0.4, 0, 0.6, 1)
local Standard = Bezier(0.4, 0, 0.2, 1) -- used for moving.
local Acceleration = Bezier(0.4, 0, 1, 1) -- used for exiting.
local Deceleration = Bezier(0, 0, 0.2, 1) -- used for entering.
-- @specs https://developer.microsoft.com/en-us/fabric#/styles/web/motion#basic-animations
local FabricStandard = Bezier(0.8, 0, 0.2, 1) -- used for moving.
local FabricAccelerate = Bezier(0.9, 0.1, 1, 0.2) -- used for exiting.
local FabricDecelerate = Bezier(0.1, 0.9, 0.2, 1) -- used for entering.
-- @specs https://docs.microsoft.com/en-us/windows/uwp/design/motion/timing-and-easing
local UWPAccelerate = Bezier(0.7, 0, 1, 0.5)
-- @specs https://www.ibm.com/design/language/elements/motion/basics
-- Productivity and Expression are both essential to an interface. Reserve Expressive motion for occasional, important moments to better capture user’s attention, and offer rhythmic break to the productive experience.
-- Use standard-easing when an element is visible from the beginning to end of a motion. Tiles expanding and table rows sorting are good examples.
local StandardProductive = Bezier(0.2, 0, 0.38, 0.9)
local StandardExpressive = Bezier(0.4, 0.14, 0.3, 1)
-- Use entrance-easing when adding elements to the view such as a modal or toaster appearing, or moving in response to users’ input, such as dropdown opening or toggle. An element quickly appears and slows down to a stop.
local EntranceProductive = Bezier(0, 0, 0.38, 0.9)
local EntranceExpressive = Bezier(0, 0, 0.3, 1)
-- Use exit-easing when removing elements from view, such as closing a modal or toaster. The element speeds up as it exits from view, implying that its departure from the screen is permanent.
local ExitProductive = Bezier(0.2, 0, 1, 0.9)
local ExitExpressive = Bezier(0.4, 0.14, 1, 1)
-- @specs https://design.firefox.com/photon/motion/duration-and-easing.html
local MozillaCurve = Bezier(0.07, 0.95, 0, 1)
local function Smooth(T)
return T * T * (3 - 2 * T)
end
local function Smoother(T)
return T * T * T * (T * (6 * T - 15) + 10)
end
local function RidiculousWiggle(T)
return math.sin(math.sin(T * 3.1415926535898) * 1.5707963267949)
end
local function Spring(T)
return 1 + (-math.exp(-6.9 * T) * math.cos(-20.106192982975 * T))
end
local function SoftSpring(T)
return 1 + (-math.exp(-7.5 * T) * math.cos(-10.053096491487 * T))
end
local function OutBounce(T)
if T < 0.36363636363636 then
return 7.5625 * T * T
elseif T < 0.72727272727273 then
return 3 + T * (11 * T - 12) * 0.6875
elseif T < 0.090909090909091 then
return 6 + T * (11 * T - 18) * 0.6875
else
return 7.875 + T * (11 * T - 21) * 0.6875
end
end
local function InBounce(T)
if T > 0.63636363636364 then
T -= 1
return 1 - T * T * 7.5625
elseif T > 0.272727272727273 then
return (11 * T - 7) * (11 * T - 3) / -16
elseif T > 0.090909090909091 then
return (11 * (4 - 11 * T) * T - 3) / 16
else
return T * (11 * T - 1) * -0.6875
end
end
local EasingFunctions = setmetatable({
InLinear = Linear;
OutLinear = Linear;
InOutLinear = Linear;
OutInLinear = Linear;
OutSmooth = Smooth;
InSmooth = Smooth;
InOutSmooth = Smooth;
OutInSmooth = Smooth;
OutSmoother = Smoother;
InSmoother = Smoother;
InOutSmoother = Smoother;
OutInSmoother = Smoother;
OutRidiculousWiggle = RidiculousWiggle;
InRidiculousWiggle = RidiculousWiggle;
InOutRidiculousWiggle = RidiculousWiggle;
OutInRidiculousWiggle = RidiculousWiggle;
OutRevBack = RevBack;
InRevBack = RevBack;
InOutRevBack = RevBack;
OutInRevBack = RevBack;
OutSpring = Spring;
InSpring = Spring;
InOutSpring = Spring;
OutInSpring = Spring;
OutSoftSpring = SoftSpring;
InSoftSpring = SoftSpring;
InOutSoftSpring = SoftSpring;
OutInSoftSpring = SoftSpring;
InSharp = Sharp;
InOutSharp = Sharp;
OutSharp = Sharp;
OutInSharp = Sharp;
InAcceleration = Acceleration;
InOutAcceleration = Acceleration;
OutAcceleration = Acceleration;
OutInAcceleration = Acceleration;
InStandard = Standard;
InOutStandard = Standard;
OutStandard = Standard;
OutInStandard = Standard;
InDeceleration = Deceleration;
InOutDeceleration = Deceleration;
OutDeceleration = Deceleration;
OutInDeceleration = Deceleration;
InFabricStandard = FabricStandard;
InOutFabricStandard = FabricStandard;
OutFabricStandard = FabricStandard;
OutInFabricStandard = FabricStandard;
InFabricAccelerate = FabricAccelerate;
InOutFabricAccelerate = FabricAccelerate;
OutFabricAccelerate = FabricAccelerate;
OutInFabricAccelerate = FabricAccelerate;
InFabricDecelerate = FabricDecelerate;
InOutFabricDecelerate = FabricDecelerate;
OutFabricDecelerate = FabricDecelerate;
OutInFabricDecelerate = FabricDecelerate;
InUWPAccelerate = UWPAccelerate;
InOutUWPAccelerate = UWPAccelerate;
OutUWPAccelerate = UWPAccelerate;
OutInUWPAccelerate = UWPAccelerate;
InStandardProductive = StandardProductive;
InStandardExpressive = StandardExpressive;
InEntranceProductive = EntranceProductive;
InEntranceExpressive = EntranceExpressive;
InExitProductive = ExitProductive;
InExitExpressive = ExitExpressive;
OutStandardProductive = StandardProductive;
OutStandardExpressive = StandardExpressive;
OutEntranceProductive = EntranceProductive;
OutEntranceExpressive = EntranceExpressive;
OutExitProductive = ExitProductive;
OutExitExpressive = ExitExpressive;
InOutStandardProductive = StandardProductive;
InOutStandardExpressive = StandardExpressive;
InOutEntranceProductive = EntranceProductive;
InOutEntranceExpressive = EntranceExpressive;
InOutExitProductive = ExitProductive;
InOutExitExpressive = ExitExpressive;
OutInStandardProductive = StandardProductive;
OutInStandardExpressive = StandardProductive;
OutInEntranceProductive = EntranceProductive;
OutInEntranceExpressive = EntranceExpressive;
OutInExitProductive = ExitProductive;
OutInExitExpressive = ExitExpressive;
OutMozillaCurve = MozillaCurve;
InMozillaCurve = MozillaCurve;
InOutMozillaCurve = MozillaCurve;
OutInMozillaCurve = MozillaCurve;
InQuad = function(T)
return T * T
end;
OutQuad = function(T)
return T * (2 - T)
end;
InOutQuad = function(T)
if T < 0.5 then
return 2 * T * T
else
return 2 * (2 - T) * T - 1
end
end;
OutInQuad = function(T)
if T < 0.5 then
T *= 2
return T * (2 - T) / 2
else
T *= 2 - 1
return (T * T) / 2 + 0.5
end
end;
InCubic = function(T)
return T * T * T
end;
OutCubic = function(T)
return 1 - (1 - T) * (1 - T) * (1 - T)
end;
InOutCubic = function(T)
if T < 0.5 then
return 4 * T * T * T
else
T -= 1
return 1 + 4 * T * T * T
end
end;
OutInCubic = function(T)
if T < 0.5 then
T = 1 - (T * 2)
return (1 - T * T * T) / 2
else
T *= 2 - 1
return T * T * T / 2 + 0.5
end
end;
InQuart = function(T)
return T * T * T * T
end;
OutQuart = function(T)
T -= 1
return 1 - T * T * T * T
end;
InOutQuart = function(T)
if T < 0.5 then
T *= T
return 8 * T * T
else
T -= 1
return 1 - 8 * T * T * T * T
end
end;
OutInQuart = function(T)
if T < 0.5 then
T *= 2 - 1
return (1 - T * T * T * T) / 2
else
T *= 2 - 1
return T * T * T * T / 2 + 0.5
end
end;
InQuint = function(T)
return T * T * T * T * T
end;
OutQuint = function(T)
T -= 1
return T * T * T * T * T + 1
end;
InOutQuint = function(T)
if T < 0.5 then
return 16 * T * T * T * T * T
else
T -= 1
return 16 * T * T * T * T * T + 1
end
end;
OutInQuint = function(T)
if T < 0.5 then
T *= 2 - 1
return (T * T * T * T * T + 1) / 2
else
T *= 2 - 1
return T * T * T * T * T / 2 + 0.5
end
end;
InBack = function(T)
return T * T * (3 * T - 2)
end;
OutBack = function(T)
return (T - 1) * (T - 1) * (T * 2 + T - 1) + 1
end;
InOutBack = function(T)
if T < 0.5 then
return 2 * T * T * (2 * 3 * T - 2)
else
return 1 + 2 * (T - 1) * (T - 1) * (2 * 3 * T - 2 - 2)
end
end;
OutInBack = function(T)
if T < 0.5 then
T *= 2
return ((T - 1) * (T - 1) * (T * 2 + T - 1) + 1) / 2
else
T *= 2 - 1
return T * T * (3 * T - 2) / 2 + 0.5
end
end;
InSine = function(T)
return 1 - math.cos(T * 1.5707963267949)
end;
OutSine = function(T)
return math.sin(T * 1.5707963267949)
end;
InOutSine = function(T)
return (1 - math.cos(3.1415926535898 * T)) / 2
end;
OutInSine = function(T)
if T < 0.5 then
return math.sin(T * 3.1415926535898) / 2
else
return (1 - math.cos((T * 2 - 1) * 1.5707963267949)) / 2 + 0.5
end
end;
OutBounce = OutBounce;
InBounce = InBounce;
InOutBounce = function(T)
if T < 0.5 then
return InBounce(2 * T) / 2
else
return OutBounce(2 * T - 1) / 2 + 0.5
end
end;
OutInBounce = function(T)
if T < 0.5 then
return OutBounce(2 * T) / 2
else
return InBounce(2 * T - 1) / 2 + 0.5
end
end;
InElastic = function(T)
return math.exp((T * 0.96380736418812 - 1) * 8) * T * 0.96380736418812 * math.sin(4 * T * 0.96380736418812) * 1.8752275007429
end;
OutElastic = function(T)
return 1 + (math.exp(8 * (0.96380736418812 - 0.96380736418812 * T - 1)) * 0.96380736418812 * (T - 1) * math.sin(4 * 0.96380736418812 * (1 - T))) * 1.8752275007429
end;
InOutElastic = function(T)
if T < 0.5 then
return (math.exp(8 * (2 * 0.96380736418812 * T - 1)) * 0.96380736418812 * T * math.sin(2 * 4 * 0.96380736418812 * T)) * 1.8752275007429
else
return 1 + (math.exp(8 * (0.96380736418812 * (2 - 2 * T) - 1)) * 0.96380736418812 * (T - 1) * math.sin(4 * 0.96380736418812 * (2 - 2 * T))) * 1.8752275007429
end
end;
OutInElastic = function(T)
-- This isn't actually correct, but it is close enough.
if T < 0.5 then
T *= 2
return (1 + (math.exp(8 * (0.96380736418812 - 0.96380736418812 * T - 1)) * 0.96380736418812 * (T - 1) * math.sin(4 * 0.96380736418812 * (1 - T))) * 1.8752275007429) / 2
else
T *= 2 - 1
return (math.exp((T * 0.96380736418812 - 1) * 8) * T * 0.96380736418812 * math.sin(4 * T * 0.96380736418812) * 1.8752275007429) / 2 + 0.5
end
end;
InExpo = function(T)
return T * T * math.exp(4 * (T - 1))
end;
OutExpo = function(T)
return 1 - (1 - T) * (1 - T) / math.exp(4 * T)
end;
InOutExpo = function(T)
if T < 0.5 then
return 2 * T * T * math.exp(4 * (2 * T - 1))
else
return 1 - 2 * (T - 1) * (T - 1) * math.exp(4 * (1 - 2 * T))
end
end;
OutInExpo = function(T)
if T < 0.5 then
T *= 2
return (1 - (1 - T) * (1 - T) / math.exp(4 * T)) / 2
else
T *= 2 - 1
return (T * T * math.exp(4 * (T - 1))) / 2 + 0.5
end
end;
InCirc = function(T)
return -(math.sqrt(1 - T * T) - 1)
end;
OutCirc = function(T)
T -= 1
return math.sqrt(1 - T * T)
end;
InOutCirc = function(T)
T *= 2
if T < 1 then
return -(math.sqrt(1 - T * T) - 1) / 2
else
T -= 2
return (math.sqrt(1 - T * T) - 1) / 2
end
end;
OutInCirc = function(T)
if T < 0.5 then
T *= 2 - 1
return math.sqrt(1 - T * T) / 2
else
T *= 2 - 1
return (-(math.sqrt(1 - T * T) - 1)) / 2 + 0.5
end
end;
}, {
__index = function(_, Index)
error(tostring(Index) .. " is not a valid easing function.", 2)
end;
})
local RunService = game:GetService("RunService")
local RawTweenFunctions = EasingFunctions
local TypeLerpers = Lerps
local Heartbeat = RunService.Heartbeat
local BoatTween = {}
local ValidStepTypes = {
["Heartbeat"] = true;
["Stepped"] = true;
["RenderStepped"] = true;
}
if not RunService:IsClient() then
ValidStepTypes.RenderStepped = nil
end
local TweenFunctions = {
FabricAccelerate = {
In = RawTweenFunctions.InFabricAccelerate;
Out = RawTweenFunctions.OutFabricAccelerate;
InOut = RawTweenFunctions.InOutFabricAccelerate;
OutIn = RawTweenFunctions.OutInFabricAccelerate;
};
UWPAccelerate = {
In = RawTweenFunctions.InUWPAccelerate;
Out = RawTweenFunctions.OutUWPAccelerate;
InOut = RawTweenFunctions.InOutUWPAccelerate;
OutIn = RawTweenFunctions.OutInUWPAccelerate;
};
Circ = {
In = RawTweenFunctions.InCirc;
Out = RawTweenFunctions.OutCirc;
InOut = RawTweenFunctions.InOutCirc;
OutIn = RawTweenFunctions.OutInCirc;
};
RevBack = {
In = RawTweenFunctions.InRevBack;
Out = RawTweenFunctions.OutRevBack;
InOut = RawTweenFunctions.InOutRevBack;
OutIn = RawTweenFunctions.OutInRevBack;
};
Spring = {
In = RawTweenFunctions.InSpring;
Out = RawTweenFunctions.OutSpring;
InOut = RawTweenFunctions.InOutSpring;
OutIn = RawTweenFunctions.OutInSpring;
};
Standard = {
In = RawTweenFunctions.InStandard;
Out = RawTweenFunctions.OutStandard;
InOut = RawTweenFunctions.InOutStandard;
OutIn = RawTweenFunctions.OutInStandard;
};
StandardExpressive = {
In = RawTweenFunctions.InStandardExpressive;
Out = RawTweenFunctions.OutStandardExpressive;
InOut = RawTweenFunctions.InOutStandardExpressive;
OutIn = RawTweenFunctions.OutInStandardExpressive;
};
Linear = {
In = RawTweenFunctions.InLinear;
Out = RawTweenFunctions.OutLinear;
InOut = RawTweenFunctions.InOutLinear;
OutIn = RawTweenFunctions.OutInLinear;
};
ExitProductive = {
In = RawTweenFunctions.InExitProductive;
Out = RawTweenFunctions.OutExitProductive;
InOut = RawTweenFunctions.InOutExitProductive;
OutIn = RawTweenFunctions.OutInExitProductive;
};
Deceleration = {
In = RawTweenFunctions.InDeceleration;
Out = RawTweenFunctions.OutDeceleration;
InOut = RawTweenFunctions.InOutDeceleration;
OutIn = RawTweenFunctions.OutInDeceleration;
};
Smoother = {
In = RawTweenFunctions.InSmoother;
Out = RawTweenFunctions.OutSmoother;
InOut = RawTweenFunctions.InOutSmoother;
OutIn = RawTweenFunctions.OutInSmoother;
};
FabricStandard = {
In = RawTweenFunctions.InFabricStandard;
Out = RawTweenFunctions.OutFabricStandard;
InOut = RawTweenFunctions.InOutFabricStandard;
OutIn = RawTweenFunctions.OutInFabricStandard;
};
RidiculousWiggle = {
In = RawTweenFunctions.InRidiculousWiggle;
Out = RawTweenFunctions.OutRidiculousWiggle;
InOut = RawTweenFunctions.InOutRidiculousWiggle;
OutIn = RawTweenFunctions.OutInRidiculousWiggle;
};
MozillaCurve = {
In = RawTweenFunctions.InMozillaCurve;
Out = RawTweenFunctions.OutMozillaCurve;
InOut = RawTweenFunctions.InOutMozillaCurve;
OutIn = RawTweenFunctions.OutInMozillaCurve;
};
Expo = {
In = RawTweenFunctions.InExpo;
Out = RawTweenFunctions.OutExpo;
InOut = RawTweenFunctions.InOutExpo;
OutIn = RawTweenFunctions.OutInExpo;
};
Sine = {
In = RawTweenFunctions.InSine;
Out = RawTweenFunctions.OutSine;
InOut = RawTweenFunctions.InOutSine;
OutIn = RawTweenFunctions.OutInSine;
};
Cubic = {
In = RawTweenFunctions.InCubic;
Out = RawTweenFunctions.OutCubic;
InOut = RawTweenFunctions.InOutCubic;
OutIn = RawTweenFunctions.OutInCubic;
};
EntranceExpressive = {
In = RawTweenFunctions.InEntranceExpressive;
Out = RawTweenFunctions.OutEntranceExpressive;
InOut = RawTweenFunctions.InOutEntranceExpressive;
OutIn = RawTweenFunctions.OutInEntranceExpressive;
};
Elastic = {
In = RawTweenFunctions.InElastic;
Out = RawTweenFunctions.OutElastic;
InOut = RawTweenFunctions.InOutElastic;
OutIn = RawTweenFunctions.OutInElastic;
};
Quint = {
In = RawTweenFunctions.InQuint;
Out = RawTweenFunctions.OutQuint;
InOut = RawTweenFunctions.InOutQuint;
OutIn = RawTweenFunctions.OutInQuint;
};
EntranceProductive = {
In = RawTweenFunctions.InEntranceProductive;
Out = RawTweenFunctions.OutEntranceProductive;
InOut = RawTweenFunctions.InOutEntranceProductive;
OutIn = RawTweenFunctions.OutInEntranceProductive;
};
Bounce = {
In = RawTweenFunctions.InBounce;
Out = RawTweenFunctions.OutBounce;
InOut = RawTweenFunctions.InOutBounce;
OutIn = RawTweenFunctions.OutInBounce;
};
Smooth = {
In = RawTweenFunctions.InSmooth;
Out = RawTweenFunctions.OutSmooth;
InOut = RawTweenFunctions.InOutSmooth;
OutIn = RawTweenFunctions.OutInSmooth;
};
Back = {
In = RawTweenFunctions.InBack;
Out = RawTweenFunctions.OutBack;
InOut = RawTweenFunctions.InOutBack;
OutIn = RawTweenFunctions.OutInBack;
};
Quart = {
In = RawTweenFunctions.InQuart;
Out = RawTweenFunctions.OutQuart;
InOut = RawTweenFunctions.InOutQuart;
OutIn = RawTweenFunctions.OutInQuart;
};
StandardProductive = {
In = RawTweenFunctions.InStandardProductive;
Out = RawTweenFunctions.OutStandardProductive;
InOut = RawTweenFunctions.InOutStandardProductive;
OutIn = RawTweenFunctions.OutInStandardProductive;
};
Quad = {
In = RawTweenFunctions.InQuad;
Out = RawTweenFunctions.OutQuad;
InOut = RawTweenFunctions.InOutQuad;
OutIn = RawTweenFunctions.OutInQuad;
};
FabricDecelerate = {
In = RawTweenFunctions.InFabricDecelerate;
Out = RawTweenFunctions.OutFabricDecelerate;
InOut = RawTweenFunctions.InOutFabricDecelerate;
OutIn = RawTweenFunctions.OutInFabricDecelerate;
};
Acceleration = {
In = RawTweenFunctions.InAcceleration;
Out = RawTweenFunctions.OutAcceleration;
InOut = RawTweenFunctions.InOutAcceleration;
OutIn = RawTweenFunctions.OutInAcceleration;
};
SoftSpring = {
In = RawTweenFunctions.InSoftSpring;
Out = RawTweenFunctions.OutSoftSpring;
InOut = RawTweenFunctions.InOutSoftSpring;
OutIn = RawTweenFunctions.OutInSoftSpring;
};
ExitExpressive = {
In = RawTweenFunctions.InExitExpressive;
Out = RawTweenFunctions.OutExitExpressive;
InOut = RawTweenFunctions.InOutExitExpressive;
OutIn = RawTweenFunctions.OutInExitExpressive;
};
Sharp = {
In = RawTweenFunctions.InSharp;
Out = RawTweenFunctions.OutSharp;
InOut = RawTweenFunctions.InOutSharp;
OutIn = RawTweenFunctions.OutInSharp;
};
}
local function Wait(Seconds)
Seconds = math.max(Seconds or 0.03, 0)
local TimeRemaining = Seconds
while TimeRemaining > 0 do
TimeRemaining -= Heartbeat:Wait()
end
return Seconds - TimeRemaining
end
function BoatTween.Create(_, Object, Data)
-- Validate
if not Object or typeof(Object) ~= "Instance" then
return warn("Invalid object to tween:", Object)
end
Data = type(Data) == "table" and Data or {}
-- Define settings
local EventStep: RBXScriptSignal = ValidStepTypes[Data.StepType] and RunService[Data.StepType] or RunService.Stepped
local TweenFunction = TweenFunctions[Data.EasingStyle or "Quad"][Data.EasingDirection or "In"]
local Time = math.max(type(Data.Time) == "number" and Data.Time or 1, 0.001)
local Goal = type(Data.Goal) == "table" and Data.Goal or {}
local DelayTime = type(Data.DelayTime) == "number" and Data.DelayTime > 0.027 and Data.DelayTime
local RepeatCount = (type(Data.RepeatCount) == "number" and math.max(Data.RepeatCount, -1) or 0) + 1
local TweenData = {}
for Property, EndValue in pairs(Goal) do
TweenData[Property] = TypeLerpers[typeof(EndValue)](Object[Property], EndValue)
end
-- Create instances
local CompletedEvent = Instance.new("BindableEvent")
local StoppedEvent = Instance.new("BindableEvent")
local ResumedEvent = Instance.new("BindableEvent")
local PlaybackConnection
local StartTime, ElapsedTime = os.clock(), 0
local TweenObject = {
["Instance"] = Object;
["PlaybackState"] = Enum.PlaybackState.Begin;
["Completed"] = CompletedEvent.Event;
["Resumed"] = ResumedEvent.Event;
["Stopped"] = StoppedEvent.Event;
}
function TweenObject.Destroy()
if PlaybackConnection then
PlaybackConnection:Disconnect()
PlaybackConnection = nil
end
CompletedEvent:Destroy()
StoppedEvent:Destroy()
ResumedEvent:Destroy()
TweenObject = nil
end
local CurrentlyReversing = false
local CurrentLayer = 0
local function Play(Layer, Reverse)
if PlaybackConnection then
PlaybackConnection:Disconnect()
PlaybackConnection = nil
end
Layer = Layer or 1
if RepeatCount ~= 0 then
if Layer > RepeatCount then
TweenObject.PlaybackState = Enum.PlaybackState.Completed
CompletedEvent:Fire()
CurrentlyReversing = false
CurrentLayer = 1
return
end
end
CurrentLayer = Layer
if Reverse then
CurrentlyReversing = true
end
if DelayTime then
TweenObject.PlaybackState = Enum.PlaybackState.Delayed;
(DelayTime < 2 and Wait or wait)(DelayTime)
end
StartTime = os.clock() - ElapsedTime
PlaybackConnection = EventStep:Connect(function()
ElapsedTime = os.clock() - StartTime
if ElapsedTime >= Time then
if Reverse then
for Property, Lerper in pairs(TweenData) do
Object[Property] = Lerper(0)
end
else
for Property, Lerper in pairs(TweenData) do
Object[Property] = Lerper(1)
end
end
PlaybackConnection:Disconnect()
PlaybackConnection = nil
if Reverse then
ElapsedTime = 0
Play(Layer + 1, false)
else
if Data.Reverses then
ElapsedTime = 0
Play(Layer, true)
else
ElapsedTime = 0
Play(Layer + 1, false)
end
end
else
local Delta = Reverse and (1 - ElapsedTime/Time) or (ElapsedTime/Time)
local Position = math.clamp(TweenFunction(Delta), 0, 1)
for Property, Lerper in pairs(TweenData) do
Object[Property] = Lerper(Position)
end
end
end)
TweenObject.PlaybackState = Enum.PlaybackState.Playing
end
function TweenObject.Play()
ElapsedTime = 0
Play(1, false)
end
function TweenObject.Stop()
if PlaybackConnection then
PlaybackConnection:Disconnect()
PlaybackConnection = nil
TweenObject.PlaybackState = Enum.PlaybackState.Cancelled
StoppedEvent:Fire()
end
end
function TweenObject.Resume()
Play(CurrentLayer, CurrentlyReversing)
ResumedEvent:Fire()
end
return TweenObject
end
return BoatTween