Hi devforum, here’s an oddly specific module that doesn’t do anything but replace boilerplate with different boilerplate!
Made this in like an hour because I was getting frustrated with making renderstep loops and trying to rack my brain around the mess i usually make when creating new loops.
Heres the usual setup:
local FinalLerp = (...) -- path
local StartingValue = -- Any supported value (i.e. Number, CFrame, Vector3, Color3, Vector2, or UDim2)
local LerpParams = FinalLerp.newLerpParams({
--[LerpParamKey] = LerpParamValue;
})
local LerpObject = FinalLerp.new(StartingValue, LerpParams)
aaaaand thats it! (Theres a bit more to it but you will see shortly)
How does this work?
Alrighty, the meat and potatoes or whatever.Lets try making a part move! Firstly, you want to create a
LerpParams
object.
We have two main values that make the basis of all of our lerping, the “LerpTime
” and LerpTarget
I put LerpTime
in quotes since in reality (because the default lerp function is asymptotic), it’s just a scaling factor determining how fast we approach the goal.
local StartingPosition = Vector3.new(0, 5, 0)
local Part = Instance.new("Part", workspace)
Part.Position = StartingPosition
Part.Anchored = true
local LerpParams = FinalLerp.newLerpParams({
LerpTime = 1; --how fast we approach the target
LerpTarget = Vector3.new(0, 10, 0); --the target to approach
})
Now that we have our lerp params, we can setup a LerpObject
.
You give it the starting value to use, the lerp params, and now we are off to the moon!
We can retrieve the LerpValue by either using LerpObject:Get()
or just LerpObject.LerpValue
.
local LerpObject = FinalLerp.new(StartingPosition , LerpParams)
LerpObject.Updated:Connect(function () --p.s. LerpObject.Updated is fired on RenderStepped (or stepped if you are on the server) AFTER the object has been lerped
Part.Position = LerpObject:Get()
end)
Um… it’s pretty boring though, since it just goes from our starting position to [0, 10, 0]
, and doesn’t really do anything else.
Thats where functions come in!
Instead of a static value, we can put a function inside of our lerp params that returns a value to be our goal.
This let’s us add more complex behavior to our lerp object.
Let’s try making it move up and down in a sine wave.
local LerpParams = FinalLerp.newLerpParams({
LerpTime = 1; --how fast we approach the target
LerpTarget = function ()
local Time = os.clock()
local Sin = math.sin(Time)
return StartingPosition + Vector3.new(0, 5 * Sin, 0)
end;
})
(You can also do this for the LerpTime if desired)
And now we have a part that moves up and down. Voila.
Current Script
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local FinalLerp = require(ReplicatedStorage.FinalLerp)
local Character = script.Parent
local Humanoid = Character:FindFirstChildWhichIsA("Humanoid")
local StartingPosition = Vector3.new(0, 5, 0)
local Part = Instance.new("Part", workspace)
Part.Position = StartingPosition
Part.Anchored = true
local LerpParams = FinalLerp.newLerpParams({
LerpTime = 1; --how fast we approach the target
LerpTarget = function ()
local Time = os.clock()
local Sin = math.sin(Time)
return StartingPosition + Vector3.new(0, 5 * Sin, 0)
end;
})
local LerpObject = FinalLerp.new(StartingPosition, LerpParams)
LerpObject.Updated:Connect(function () --p.s. LerpObject.Updated is fired on RenderStepped (or stepped if you are on the server) AFTER the object has been lerped
Part.Position = LerpObject:Get()
end)
--[[
ALTERNATIVE TO USING LerpTarget = function() end
LerpObject.Updated:Connect(function (dt: number)
local Time = os.clock()
local Sin = math.sin(Time)
local TargetPosition = StartingPosition + Vector3.new(0, 5 * Sin, 0)
LerpObject:SetTarget(TargetPosition)
Part.Position = LerpObject:Get()
TimeElapsed += dt
end)
]]
Well, now I’m bored again and I want to make it fly off into space after 10 seconds.
(this is just a way to showcase lerp “failing”)
Using “lerp failing” (not too sure what to call it) we can easily add a second target and time to our LerpParams
.
“LerpFailing” is just a way to use a different lerp time and target without too much hassle.
Here’s where i’ll introduce 3 new paramaters:
LerpFailTime
, which works the same as LerpTime
.
LerpFailTarget
, which works the same as LerpTarget
.
And, LerpCondition
which determines which set of values to use.
Basically, whenever we want to update our LerpValue, we call LerpCondition, which will output a boolean
(any non-nil value works too and acts as true).
If LerpCondition() → true then we use the default LerpTime and LerpTarget values.
But, if LerpCondition () → false, then we start using the LerpFailTime and LerpFailTarget.
This lets us easily add some logic to our lerps without having to constantly change the target in a loop.
So, lets make our part fly off!
local TimeElapsed = 0
local LerpParams = FinalLerp.newLerpParams({
LerpTime = 1;
LerpTarget = function ()
local Time = os.clock()
local Sin = math.sin(Time)
return StartingPosition + Vector3.new(0, 5 * Sin, 0) -- will be used if LerpCondition() == true
end;
LerpFailTime = 5;
LerpFailTarget = function ()
return Vector3.new(0, 1000, 0) -- will be used if LerpCondition() == false
end,
LerpCondition = function ()
return TimeElapsed < 10; -- true if less than 10 seconds have passed
end,
})
local LerpObject = FinalLerp.new(StartingPosition, LerpParams)
LerpObject.Updated:Connect(function (dt: number)
Part.Position = LerpObject:Get()
TimeElapsed += dt -- add DeltaTime to TimeElapsed
end)
Boom! There it goes.
NOTE:
LerpFailTime and LerpFailTarget will default to LerpTime and LerpTarget respectively if they are not set.
Final Script
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local FinalLerp = require(ReplicatedStorage.FinalLerp)
local Character = script.Parent
local Humanoid = Character:FindFirstChildWhichIsA("Humanoid")
local StartingPosition = Vector3.new(0, 5, 0)
local Part = Instance.new("Part", workspace)
Part.Position = StartingPosition
Part.Anchored = true
local TimeElapsed = 0
local LerpParams = FinalLerp.newLerpParams({
LerpTime = 1;
LerpTarget = function ()
local Time = os.clock()
local Sin = math.sin(Time)
return StartingPosition + Vector3.new(0, 5 * Sin, 0)
end;
LerpFailTime = 5;
LerpFailTarget = function ()
return Vector3.new(0, 1000, 0)
end,
LerpCondition = function ()
return TimeElapsed < 10;
end,
})
local LerpObject = FinalLerp.new(StartingPosition, LerpParams)
LerpObject.Updated:Connect(function (dt: number)
Part.Position = LerpObject:Get()
TimeElapsed += dt
end)
i was thinking about making an array of possible lerp targets to use and then having you select which one but intellisense doesnt really allow for that and also i got hit with the curse of lazy so ehhh whatever
Heres where I confess… I HAVE BEEN LYING TO YOU THIS WHOLE TIME!!
Ok, well. Not really. But the default lerp function that comes packaged with the module is actually eased! I just think it looks better. If you don’t like it though, you can change that!
This kind of defeats the purpose of it being easy to set up but if you want to do it I won’t stop you!
Instead of the module doing the easing for you, you are now in control!
Just return whatever value you want and thats where the LerpValue will be.
CustomLerp: (LerpValue: SupportedTypes, Target: SupportedTypes, dt: number) -> (SupportedTypes)
CustomFailLerp: (LerpValue: SupportedTypes, Target: SupportedTypes, dt: number) -> (SupportedTypes)
What about another example?
Heres a pretty basic one: Say you want to have a character that has acceleration, and speeds up over time the longer they have been walking for.First, we can setup the our LerpObject, like normal.
Step 1
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local FinalLerp = require(ReplicatedStorage.FinalLerp)
local Character = script.Parent
local Humanoid = Character:FindFirstChildWhichIsA("Humanoid")
local LerpParams = FinalLerp.newLerpParams({
LerpTime = .5;
LerpTarget = 1;
})
local LerpObj = FinalLerp.new(0, LerpParams)
Now, we can add our condition, and update the params to have our fail target and maybe make it ease back faster, and we can snap to make sure we reach the max walkspeed.
Step 2
local LerpParams = FinalLerp.newLerpParams({
LerpTime = .5;
LerpTarget = 1;
LerpFailTime = .2;
LerpFailTarget = 0;
Snap = .05; -- Gets the distance between value and target, if its less than the snap then instantly snap to the target!
LerpCondition = function ()
return Humanoid.MoveDirection.Magnitude > .1 --not moving counts as 'failing' the lerp, so move closer to the LerpFailTarget.
end,
})
Lastly, we just update the humanoid’s walkspeed whenever the value is updated.
Step 3
local TargetWalkspeed = 24
LerpObj.Updated:Connect(function ()
Humanoid.WalkSpeed = TargetWalkspeed * LerpObj:Get() --Lerp value is going to range from [0-1], multiplying that by our target walkspeed will give us a inbetween value
end)
Great! It works, and you didn’t need to do any math!
Final Script
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local FinalLerp = require(ReplicatedStorage.FinalLerp)
local Character = script.Parent
local Humanoid = Character:FindFirstChildWhichIsA("Humanoid")
local LerpParams = FinalLerp.newLerpParams({
LerpTime = .5;
LerpTarget = 1;
LerpFailTime = .2;
LerpFailTarget = 0;
Snap = .05;
LerpCondition = function ()
return Humanoid.MoveDirection.Magnitude > .1
end,
})
local LerpObj = FinalLerp.new(0, LerpParams)
local TargetWalkspeed = 24
LerpObj.Updated:Connect(function ()
Humanoid.WalkSpeed = TargetWalkspeed * LerpObj:Get()
end)
Why should I use this over any other options?
*shrugs* not sure! This was an internal tool for me and i mostly used it for visual effects but I realized that this might be useful to other people, which is why I'm releasing it.Types:
LerpParams (Click To Expand)
export type LerpParams = {
LerpTime: number | () -> (number); --determines how fast we reach the goal (IS NOT THE ACTUAL TIME TO REACH)
LerpFailTime: number? | () -> (number); --determines how fast we reach the fail goal
LerpTarget: SupportedTypes | (dt: number) -> (SupportedTypes); --the goal in question
LerpFailTarget: SupportedTypes? | (dt: number) -> (SupportedTypes)?; --the fail goal in question
Snap: number?; --Snaps to the target if the distance from the target is smaller than this snap paramater (ONLY WORKS FOR NUMBERS, NOT ANY OTHER TYPES SORRY :( )
LerpCondition: () -> (boolean); --Determines whether or not to use lerptarget/time or lerpfailtarget/time
CustomLerp: (LerpValue: SupportedTypes, Target: SupportedTypes, dt: number) -> (SupportedTypes);
CustomFailLerp: (LerpValue: SupportedTypes, Target: SupportedTypes, dt: number) -> (SupportedTypes);
};
LerpObject (Click To Expand)
export type LerpObject = {
StartingValue: SupportedTypes;
LerpValue: SupportedTypes;
Target: SupportedTypes;
--Internal methods, usually you don't want to call these. Just use LerpObject.LerpParams.
GetLerpCondition: () -> (boolean);
GetCustomLerp: (LerpTarget: SupportedTypes, Condition: boolean, dt: number) -> (SupportedTypes);
GetLerpTarget: (Condition: boolean, dt: number) -> (SupportedTypes);
GetLerpTime: (Condition: boolean) -> (number);
--Deprecated methods
SetTarget: (SupportedTypes) -> ();
SetLerpCondition: (() -> (boolean?)) -> ();
GetLerpParams: () -> (LerpParams);
Get: () -> (SupportedTypes);
Destroy: () -> ();
--[[
// Aliases
GetValue -> Get;
]]
};
GIMME DOWNLOAD NOW
ok!As always, you can grab this from the toolbox here.
Or as a .rbxm file if you prefer!
FinalLerpV1.15.rbxm (10.2 KB)
Obligatory feedback poll because of course.
- This is useful, helps with workflow/makes it easier to make or understand lerps.
- This may be useful, but I don’t think I will use it in my project due to technical debt/difficulty of integration, etc.
- This isn’t very helpful, it’s unnecessarily complicated for little gain.
- This isn’t very helpful for some other reason / I have a grievance with how it’s set up (please reply below)
0 voters
Please also reply with any other feedback or bug reports! Thanks :3