W0W. Very interesting module. I’ll definitely experiment with this!
Hello. I have some suggestions:
First of all. Can i control the direction of cframes or rotation since it’s a periodical value. it can rotate either from the negative or positive side. and with cframe tweening, the direction was unexpected. at least to my knowledge.
Secondly. can you provide us with a function that you can supply it with a number from 0 to 1. and it gives you the resulted properties values without needing to tween. or in another word, being able to change the time position of the tween playback.
However the modules looks so cool, thank you so much. i wish you take my suggestions into considerations so i can use it for my plugins
Will you ever make a version of your module for roact?
Oh my god lol, what does that even mean? I know a decent amount of scripting but my god what does ANY of that mean?
It’s just a function that lerps a Color3 value. Basically, you give it 2 Color3
values: one as the beginning value, and the other as the ending value. That function then returns another function, in which you give the value between 0
and 1
to get the percentage of the transition there.
For example: If I wanted to lerp a Color3 from (0, 0, 0) to (1, 1, 1), and I inputted 0.5, I’d get the Color3 halfway between the beginning and ending Color3 values, which would be (0.5, 0.5, 0.5).
local BeginningColor = Color3.new(0, 0, 0)
local EndingColor = Color3.new(1, 1, 1)
local Interpolation = Lerps.Color3(BeginningColor, EndingColor)
print(Interpolation(0.5)) -- Output: 0.5, 0.5, 0.5
If you’re still confused on lerps and the process of interpolation, I found a nice article that explains it quite well.
Hi @boatbomber, thank you for sharing your resources!
I also have a feature request:
- add Repeating & Repeated Event (Tween.Repeat)
- add Delaying & Delayed event (Tween.Delay)
- add Reversing & Reversed event (Tween.Reverse)
BoatTween is 4 ModuleScripts, a parent with 3 children.
- BoatTween
- Lerps
- TweenFunctions
- Bezier
Source Code:
BoatTween
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 BoatTween = {}
local RunService = game:GetService("RunService")
local ValidStepTypes = {
["Heartbeat"] = true;
["Stepped"] = true;
["RenderStepped"] = true;
}
if not RunService:IsClient() then
ValidStepTypes.RenderStepped = nil
end
local RawTweenFunctions = require(script.TweenFunctions)
local TweenFunctions = {
Linear = {
In = RawTweenFunctions.Linear;
Out = RawTweenFunctions.Linear;
InOut = RawTweenFunctions.Linear;
};
Quad = {
In = RawTweenFunctions.InQuad;
Out = RawTweenFunctions.OutQuad;
InOut = RawTweenFunctions.InOutQuad;
};
Cubic = {
In = RawTweenFunctions.InCubic;
Out = RawTweenFunctions.OutCubic;
InOut = RawTweenFunctions.InOutCubic;
};
Quart = {
In = RawTweenFunctions.InQuart;
Out = RawTweenFunctions.OutQuart;
InOut = RawTweenFunctions.InOutQuart;
};
Quint = {
In = RawTweenFunctions.InQuint;
Out = RawTweenFunctions.OutQuint;
InOut = RawTweenFunctions.InOutQuint;
};
Sine = {
In = RawTweenFunctions.InSine;
Out = RawTweenFunctions.OutSine;
InOut = RawTweenFunctions.InOutSine;
};
Expo = {
In = RawTweenFunctions.InExpo;
Out = RawTweenFunctions.OutExpo;
InOut = RawTweenFunctions.InOutExpo;
};
Circ = {
In = RawTweenFunctions.InCirc;
Out = RawTweenFunctions.OutCirc;
InOut = RawTweenFunctions.InOutCirc;
};
Elastic = {
In = RawTweenFunctions.InElastic;
Out = RawTweenFunctions.OutElastic;
InOut = RawTweenFunctions.InOutElastic;
};
Back = {
In = RawTweenFunctions.InBack;
Out = RawTweenFunctions.OutBack;
InOut = RawTweenFunctions.InOutBack;
};
Bounce = {
In = RawTweenFunctions.InBounce;
Out = RawTweenFunctions.OutBounce;
InOut = RawTweenFunctions.InOutBounce;
};
Smooth = {
In = RawTweenFunctions.InSmooth;
Out = RawTweenFunctions.OutSmooth;
InOut = RawTweenFunctions.InOutSmooth;
};
Smoother = {
In = RawTweenFunctions.InSmoother;
Out = RawTweenFunctions.OutSmoother;
InOut = RawTweenFunctions.InOutSmoother;
};
RidiculousWiggle = {
In = RawTweenFunctions.InRidiculousWiggle;
Out = RawTweenFunctions.OutRidiculousWiggle;
InOut = RawTweenFunctions.InOutRidiculousWiggle;
};
RevBack = {
In = RawTweenFunctions.InRevBack;
Out = RawTweenFunctions.OutRevBack;
InOut = RawTweenFunctions.InOutRevBack;
};
Spring = {
In = RawTweenFunctions.InSpring;
Out = RawTweenFunctions.OutSpring;
InOut = RawTweenFunctions.InOutSpring;
};
SoftSpring = {
In = RawTweenFunctions.InSoftSpring;
Out = RawTweenFunctions.OutSoftSpring;
InOut = RawTweenFunctions.InOutSoftSpring;
};
Sharp = {
In = RawTweenFunctions.InSharp;
Out = RawTweenFunctions.OutSharp;
InOut = RawTweenFunctions.InOutSharp;
};
Acceleration = {
In = RawTweenFunctions.InAcceleration;
Out = RawTweenFunctions.OutAcceleration;
InOut = RawTweenFunctions.InOutAcceleration;
};
Standard = {
In = RawTweenFunctions.InStandard;
Out = RawTweenFunctions.OutStandard;
InOut = RawTweenFunctions.InOutStandard;
};
Deceleration = {
In = RawTweenFunctions.InDeceleration;
Out = RawTweenFunctions.OutDeceleration;
InOut = RawTweenFunctions.InOutDeceleration;
};
FabricStandard = {
In = RawTweenFunctions.InFabricStandard;
Out = RawTweenFunctions.OutFabricStandard;
InOut = RawTweenFunctions.InOutFabricStandard;
};
FabricAccelerate = {
In = RawTweenFunctions.InFabricAccelerate;
Out = RawTweenFunctions.OutFabricAccelerate;
InOut = RawTweenFunctions.InOutFabricAccelerate;
};
FabricDecelerate = {
In = RawTweenFunctions.InFabricDecelerate;
Out = RawTweenFunctions.OutFabricDecelerate;
InOut = RawTweenFunctions.InOutFabricDecelerate;
};
UWPAccelerate = {
In = RawTweenFunctions.InUWPAccelerate;
Out = RawTweenFunctions.OutUWPAccelerate;
InOut = RawTweenFunctions.InOutUWPAccelerate;
};
StandardProductive = {
In = RawTweenFunctions.InStandardProductive;
Out = RawTweenFunctions.OutStandardProductive;
InOut = RawTweenFunctions.InOutStandardProductive;
};
EntranceProductive = {
In = RawTweenFunctions.InEntranceProductive;
Out = RawTweenFunctions.OutEntranceProductive;
InOut = RawTweenFunctions.InOutEntranceProductive;
};
ExitProductive = {
In = RawTweenFunctions.InExitProductive;
Out = RawTweenFunctions.OutExitProductive;
InOut = RawTweenFunctions.InOutExitProductive;
};
StandardExpressive = {
In = RawTweenFunctions.InStandardExpressive;
Out = RawTweenFunctions.OutStandardExpressive;
InOut = RawTweenFunctions.InOutStandardExpressive;
};
EntranceExpressive = {
In = RawTweenFunctions.InEntranceExpressive;
Out = RawTweenFunctions.OutEntranceExpressive;
InOut = RawTweenFunctions.InOutEntranceExpressive;
};
ExitExpressive = {
In = RawTweenFunctions.InExitExpressive;
Out = RawTweenFunctions.OutExitExpressive;
InOut = RawTweenFunctions.InOutExitExpressive;
};
MozillaCurve = {
In = RawTweenFunctions.InMozillaCurve;
Out = RawTweenFunctions.OutMozillaCurve;
InOut = RawTweenFunctions.InOutMozillaCurve;
};
}
local TypeLerpers = require(script.Lerps)
function BoatTween:Create(Object,Data)
-- Validate
if not Object or typeof(Object)~= "Instance" then
warn("Invalid object to tween:", Object)
return
end
Data = type(Data) == "table" and Data or {}
-- Define settings
local EventStep = ValidStepTypes[Data.StepType] and RunService[Data.StepType] or RunService.Stepped
local TweenFunction = TweenFunctions[Data.EasingStyle or "Quad"][Data.EasingDirection or "In"]
local Time = math.clamp(type(Data.Time)=="number" and Data.Time or 1,0,9999999999)
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.clamp(Data.RepeatCount,-1,999999999) 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 = tick(),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 Lerp = TypeLerpers
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
wait(DelayTime)
end
StartTime = tick()-ElapsedTime
PlaybackConnection = EventStep:Connect(function()
ElapsedTime = tick()-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
if Reverse then
for Property,Lerper in pairs(TweenData) do
Object[Property] = Lerper(math.clamp(TweenFunction(1-(ElapsedTime/Time)),0,1))
end
else
for Property,Lerper in pairs(TweenData) do
Object[Property] = Lerper(math.clamp(TweenFunction(ElapsedTime/Time),0,1))
end
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```
Lerps
local BLACK_COLOR3 = Color3.new()
-- Generic Roblox DataType lerp function.
local function RobloxLerp(V0, V1)
return function(DeltaTime)
return V0:Lerp(V1, DeltaTime)
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 Lerps = setmetatable({
boolean = function(V0, V1)
return function(DeltaTime)
if DeltaTime < 0.5 then
return V0
else
return V1
end
end
end;
number = function(V0, V1)
local Delta = V1 - V0
return function(DeltaTime)
return V0 + Delta * DeltaTime
end
end;
CFrame = RobloxLerp;
Color3 = function(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 > 0.0000000000000010000000000000001 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 > 0.0000000000000010000000000000001 then -- 1E-15
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(DeltaTime)
local L = (1 - DeltaTime) * L0 + DeltaTime * L1
if L < 0.0197955 then
return BLACK_COLOR3
end
local U = ((1 - DeltaTime) * U0 + DeltaTime * U1) / L + 0.19783
local V = ((1 - DeltaTime) * V0 + DeltaTime * 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 < 0.0031306684424999998 and 12.92 * R or 1.055 * R ^ (1 / 2.4) - 0.055 -- 3.1306684425E-3
G = G < 0.0031306684424999998 and 12.92 * G or 1.055 * G ^ (1 / 2.4) - 0.055
B = B < 0.0031306684424999998 and 12.92 * B or 1.055 * B ^ (1 / 2.4) - 0.055
return Color3.new(
R > 1 and 1 or R < 0 and 0 or R,
G > 1 and 1 or G < 0 and 0 or G,
B > 1 and 1 or B < 0 and 0 or B
)
end
end;
NumberRange = function(V0, V1)
local Min0, Max0 = V0.Min, V0.Max
local DeltaMin, DeltaMax = V1.Min - Min0, V1.Max - Max0
return function(DeltaTime)
return NumberRange.new(Min0 + DeltaTime * DeltaMin, Max0 + DeltaTime * DeltaMax)
end
end;
NumberSequenceKeypoint = function(Start, End)
local T0, V0, E0 = Start.Time, Start.Value, Start.Envelope
local DT, DV, DE = End.Time - T0, End.Value - V0, End.Envelope - E0
return function(DeltaTime)
return NumberSequenceKeypoint.new(T0 + DeltaTime * DT, V0 + DeltaTime * DV, E0 + DeltaTime * 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(DeltaTime)
return PhysicalProperties.new(
D0 + DeltaTime * DD,
E0 + DeltaTime * DE, EW0 + DeltaTime * DEW,
F0 + DeltaTime * DF, FW0 + DeltaTime * 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(DeltaTime)
return Ray.new(
Vector3.new(OX0 + DeltaTime * DOX, OY0 + DeltaTime * DOY, OZ0 + DeltaTime * DOZ),
Vector3.new(DX0 + DeltaTime * DDX, DY0 + DeltaTime * DDY, DZ0 + DeltaTime * 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(DeltaTime)
return UDim.new(SC + DeltaTime * DSC, OF + DeltaTime * DOF)
end
end;
UDim2 = RobloxLerp;
Vector2 = RobloxLerp;
Vector3 = RobloxLerp;
Rect = function(V0, V1)
return function(DeltaTime)
return Rect.new(
V0.Min.X + DeltaTime * (V1.Min.X - V0.Min.X), V0.Min.Y + DeltaTime * (V1.Min.Y - V0.Min.Y),
V0.Max.X + DeltaTime * (V1.Max.X - V0.Max.X), V0.Max.Y + DeltaTime * (V1.Max.Y - V0.Max.Y)
)
end
end;
Region3 = function(V0, V1)
return function(DeltaTime)
local imin = Lerp(V0.CFrame * (-V0.Size / 2), V1.CFrame * (-V1.Size / 2), DeltaTime)
local imax = Lerp(V0.CFrame * (V0.Size / 2), V1.CFrame * (V1.Size / 2), DeltaTime)
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(DeltaTime)
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
local interValue = (bValue - ap.Value) * DeltaTime + ap.Value
local interEnvelope = (bEnvelope - ap.Envelope) * DeltaTime + ap.Envelope
local interp = NumberSequenceKeypoint.new(ap.Time, interValue, interEnvelope)
keylength = keylength + 1
keypoints[keylength] = interp
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
local interValue = (bp.Value - aValue) * DeltaTime + aValue
local interEnvelope = (bp.Envelope - aEnvelope) * DeltaTime + aEnvelope
local interp = NumberSequenceKeypoint.new(bp.Time, interValue, interEnvelope)
keylength = keylength + 1
keypoints[keylength] = interp
end
end
table.sort(keypoints, SortByTime)
return NumberSequence.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;
})
return Lerps```
(TweenFunctions and Bezier in subsequent reply for length limitations)
Why you want to read this on a device that is not a PC or Mac, why this isn’t currently showing up on boatbomber’s GitHub, and why I bothered to post this probably all have some common overlap. Venn diagram as homework for the ambitious.
TweenFunctions
local function RevBack(T)
T = 1 - T
return 1 - (math.sin(T * 1.5707963267948965579989817342720925807952880859375) +
(math.sin(T * 3.141592653589793115997963468544185161590576171875) *
(math.cos(T * 3.141592653589793115997963468544185161590576171875) + 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.141592653589793115997963468544185161590576171875) * 1.5707963267948965579989817342720925807952880859375)
end
local function Spring(T)
return 1 + (-math.exp(-6.9 * T) * math.cos(-20.1061929829746759423869661986827850341796875 * T))
end
local function SoftSpring(T)
return 1 + (-math.exp(-7.5 * T) * math.cos(-10.05309649148733797119348309934139251708984375 * T))
end
local function OutBounce(T)
return T < 0.36363636363636364645657295113778673112392425537109375 and 7.5625 * T * T
or T < 0.7272727272727272929131459022755734622478485107421875 and 3 + T * (11 * T - 12) * 0.6875
or T < 0.90909090909090906063028114658663980662822723388671875 and 6 + T * (11 * T - 18) * 0.6875
or 7.875 + T * (11 * T - 21) * 0.6875
end
local function InBounce(T)
if T > 0.63636363636363635354342704886221326887607574462890625 then
T = T - 1
return 1 - T * T * 7.5625
elseif T > 0.2727272727272727070868540977244265377521514892578125 then
return (11 * T - 7) * (11 * T - 3) / -16
elseif T > 0.0909090909090909116141432377844466827809810638427734375 then
return (11 * (4 - 11 * T) * T - 3) / 16
else
return T * (11 * T - 1) * -0.6875
end
end
return setmetatable({
Linear = Linear;
OutSmooth = Smooth;
InSmooth = Smooth;
InOutSmooth = Smooth;
OutSmoother = Smoother;
InSmoother = Smoother;
InOutSmoother = Smoother;
OutRidiculousWiggle = RidiculousWiggle;
InRidiculousWiggle = RidiculousWiggle;
InOutRidiculousWiggle = RidiculousWiggle;
OutRevBack = RevBack;
InRevBack = RevBack;
InOutRevBack = RevBack;
OutSpring = Spring;
InSpring = Spring;
InOutSpring = Spring;
OutSoftSpring = SoftSpring;
InSoftSpring = SoftSpring;
InOutSoftSpring = SoftSpring;
InSharp = Sharp;
InOutSharp = Sharp;
OutSharp = Sharp;
InAcceleration = Acceleration;
InOutAcceleration = Acceleration;
OutAcceleration = Acceleration;
InStandard = Standard;
InOutStandard = Standard;
OutStandard = Standard;
InDeceleration = Deceleration;
InOutDeceleration = Deceleration;
OutDeceleration = Deceleration;
InFabricStandard = FabricStandard;
InOutFabricStandard = FabricStandard;
OutFabricStandard = FabricStandard;
InFabricAccelerate = FabricAccelerate;
InOutFabricAccelerate = FabricAccelerate;
OutFabricAccelerate = FabricAccelerate;
InFabricDecelerate = FabricDecelerate;
InOutFabricDecelerate = FabricDecelerate;
OutFabricDecelerate = FabricDecelerate;
InUWPAccelerate = UWPAccelerate;
InOutUWPAccelerate = UWPAccelerate;
OutUWPAccelerate = UWPAccelerate;
InStandardProductive = StandardProductive;
OutStandardProductive = StandardProductive;
InOutStandardProductive = StandardProductive;
InStandardExpressive = StandardExpressive;
OutStandardExpressive = StandardExpressive;
InOutStandardExpressive = StandardExpressive;
InEntranceProductive = EntranceProductive;
OutEntranceProductive = EntranceProductive;
InOutEntranceProductive = EntranceProductive;
InEntranceExpressive = EntranceExpressive;
OutEntranceExpressive = EntranceExpressive;
InOutEntranceExpressive = EntranceExpressive;
InExitProductive = ExitProductive;
OutExitProductive = ExitProductive;
InOutExitProductive = ExitProductive;
InExitExpressive = ExitExpressive;
OutExitExpressive = ExitExpressive;
InOutExitExpressive = ExitExpressive;
OutMozillaCurve = MozillaCurve;
InMozillaCurve = MozillaCurve;
InOutMozillaCurve = MozillaCurve;
InQuad = function(T)
return T * T
end;
OutQuad = function(T)
return T * (2 - T)
end;
InOutQuad = function(T)
return T < 0.5 and 2 * T * T or 2 * (2 - T) * T - 1
end;
InCubic = function(T)
return T * T * T
end;
OutCubic = function(T)
return 1 - (1 - T) * (1 - T) * (1 - T)
end;
InOutCubic = function(T)
return T < 0.5 and 4 * T * T * T or 1 + 4 * (T - 1) * (T - 1) * (T - 1)
end;
InQuart = function(T)
return T * T * T * T
end;
OutQuart = function(T)
T = T - 1
return 1 - T * T * T * T
end;
InOutQuart = function(T)
if T < 0.5 then
T = T * T
return 8 * T * T
else
T = T - 1
return 1 - 8 * T * T * T * T
end;
end;
InQuint = function(T)
return T * T * T * T * T
end;
OutQuint = function(T)
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 = T - 1
return 16 * T * T * T * T * T + 1
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)
return T < 0.5 and 2 * T * T * (2 * 3 * T - 2) or 1 + 2 * (T - 1) * (T - 1) * (2 * 3 * T - 2 - 2)
end;
InSine = function(T)
return 1 - math.cos(T * 1.5707963267948965579989817342720925807952880859375)
end;
OutSine = function(T)
return math.sin(T * 1.5707963267948965579989817342720925807952880859375)
end;
InOutSine = function(T)
return (1 - math.cos(3.141592653589793115997963468544185161590576171875 * T)) / 2
end;
OutBounce = OutBounce;
InBounce = InBounce;
InOutBounce = function(T)
return T < 0.5 and InBounce(2 * T) / 2 or OutBounce(2 * T - 1) / 2 + 0.5
end;
InElastic = function(T)
return math.exp((T * 0.9638073641881153008625915390439331531524658203125 - 1) * 8) * T *
0.9638073641881153008625915390439331531524658203125 *
math.sin(4 * T * 0.9638073641881153008625915390439331531524658203125) * 1.8752275007428715891677484250976704061031341552734375
end;
OutElastic = function(T)
return 1 +
(math.exp(8 * (0.9638073641881153008625915390439331531524658203125 - 0.9638073641881153008625915390439331531524658203125 * T - 1)) *
0.9638073641881153008625915390439331531524658203125 * (T - 1) *
math.sin(4 * 0.9638073641881153008625915390439331531524658203125 * (1 - T))) * 1.8752275007428715891677484250976704061031341552734375
end;
InOutElastic = function(T)
return T < 0.5 and (math.exp(8 * (2 * 0.9638073641881153008625915390439331531524658203125 * T - 1)) * 0.9638073641881153008625915390439331531524658203125 * T * math.sin(2 * 4 * 0.9638073641881153008625915390439331531524658203125 * T)) * 1.8752275007428715891677484250976704061031341552734375
or 1 + (math.exp(8 * (0.9638073641881153008625915390439331531524658203125 * (2 - 2 * T) - 1)) * 0.9638073641881153008625915390439331531524658203125 * (T - 1) * math.sin(4 * 0.9638073641881153008625915390439331531524658203125 * (2 - 2 * T))) * 1.8752275007428715891677484250976704061031341552734375
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)
return T < 0.5 and 2 * T * T * math.exp(4 * (2 * T - 1)) or 1 - 2 * (T - 1) * (T - 1) * math.exp(4 * (1 - 2 * T))
end;
InCirc = function(T)
return -(math.sqrt(1 - T * T) - 1)
end;
OutCirc = function(T)
T = T - 1
return math.sqrt(1 - T * T)
end;
InOutCirc = function(T)
T = T * 2
if T < 1 then
return -(math.sqrt(1 - T * T) - 1) / 2
else
T = T - 2
return (math.sqrt(1 - T * T) - 1) / 2
end
end;
}, {
__index = function(_, Index)
error(tostring(Index) .. " is not a valid easing style.", 2)
end;
})```
Bezier
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 = IntervalStart + 0.1
CurrentSample = CurrentSample + 1
end
CurrentSample = 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 = GuessForT - CurrentX / CurrentSlope
end
GuessT = GuessForT
elseif InitialSlope == 0 then
GuessT = GuessForT
else
local AB = IntervalStart + 0.1
local CurrentX, CurrentT, Index = 0, nil
while (CurrentX >= 0 and CurrentX or 0 - 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 = Index + 1
end
GuessT = CurrentT
end
return (((1 - 3 * Y2 + 3 * Y1) * GuessT + (3 * Y2 - 6 * Y1)) * GuessT + (3 * Y1)) * GuessT
end
end
return Bezier```
I think the statement is pretty self explanatory
Thanks for your efforts.
You can format your code to display correctly by Using the </> button or change it to
— Code
By using 3 ` at the start and end
I used that button, but apparently it only inserts one ` at beginning and end. Have edited those two replies to use ``` now.
Not a question, was pointing out the reason is “because you want to”.
A very nice resource for smooth tweening, especially on the server.
Appreciate the resource, and I will experiment with it in larger projects soon!
Maybe even games like No-Scope Sniping and Infinite Autocorrecy can use it???
I want a part to follow a track of bricks. I also want to decelerate on a specific duration to a specific duration, is that possible with this module? If so, how?
Found an issue: can not have multiple Tweens created on a single Instance at a time
Use case: in a case where one might want to save multiple Tweens instead of creating new ones every time upon usage
Code
local part = script.Part; part.Parent = workspace
local TweenS = require(script.BoatTween)
local tween1 = TweenS:Create(part,{
Goal = {
Position = Vector3.new(0,10,0)
}
})
local tween2 = TweenS:Create(part,{
Goal = {
Position = Vector3.new(0,0,0)
}
})
local DB;
part.ClickDetector.MouseClick:Connect(function()
if not DB then
DB = true
tween1:Play()
wait(2)
tween2:Play()
wait(1)
DB = false
end
end)
repro.rbxl (33.7 KB)
I found the root cause of the issue that I have reported:
You are storing the Value not the Instance it self, so while the Part.Position changes the Value stays the same.
It’s like doing
Value = NumberValue.Value
Instead of
Value = NumberValue
If you aren’t going to maintain this Module I can step up, @boatbomber if you would allow that
I’ll make a GitHub repo for this over the weekend if you’d like to fork and make contributions!
Edit: Done. @RuizuKun_Dev
A little more than that actually. To be more specific, it’s a CIELUV lerp, which is much more accurate and better than the RGB lerp that Color3::Lerp
uses.
Thanks for letting me know! I will do some research regarding this soon.
How can I see the default values of the data dictionary?
i used
Tween.Completed:Connect(Tween.Destroy)
but my ui just freeze after it?
I’ll give this a run, looking forward to trying it out!