BoatTween module

Oops, you’re right. It’s probably better named PlayCount with that behavior.
I’ll alter the behavior on Sunday, thanks for bringing it to my attention.

Edit: Saturday night, and I fixed this. :slight_smile:

Repeat count behaves the same as TweenService now, with 3 RepeatCount being 4 plays, 0 being 1 play, and so on. -1 is still inf.

2 Likes

yeye i was like omg why isnt this working for like 2 hours then i figured out

2 Likes

Update: Minor bug fixes (clamping input to avoid edge cases) and behavior improvements (RepeatCount follows the TweenService behaviors).

1 Like

i never understood some of the easing styles of the normal tweenservice i tried googling some of the new one you added with no result

@boatbomber I noticed some unexplainable weird behavior a code i wrote to update the cam position worked fine with the default TweenService but when i tried using your module (with the same setting i used before) it started behaving weirdly and the movement was jittery and very hard to look at

here is the code


--server-side
game:GetService('RunService').Heartbeat:Connect(function()
	UpdateCamEvent:FireAllClients(GetBoundingBox(Targets))
end)


--client with normal TweenService (works fine)
local Tweeninfo = TweenInfo.new (0.5,Enum.EasingStyle.Quad,Enum.EasingDirection.Out,0,false,0)
local function UpdateCamFunc(info)
	local Center = info[1]
	local Size = info[2]
	local Tween = TweenService:Create(camera,Tweeninfo,{["CFrame"]=CFrame.new(Center.X,Center.Y,0)+Offset})
	Tween:Play()
end
UpdateCamEvent.OnClientEvent:Connect(UpdateCamFunc)

--client with BoatTween (not so much)
local function UpdateCamFunc(info)
	local Center = info[1]
	local Size = info[2]
	local BoatTweenInfo = {Time = 0.5;EasingStyle = "Quad";EasingDirection = "Out";Reverses = false;DelayTime = 0;RepeatCount = 0;StepType = "Heartbeat";Goal= {["CFrame"]=CFrame.new(Center.X,Center.Y,0)+Offset}};
	local Tween = BoatTween:Create(camera,BoatTweenInfo)
	Tween:Play()
end
UpdateCamEvent.OnClientEvent:Connect(UpdateCamFunc)

here what it looks like
the gif is a bit laggy but if you tested it you will see for yourself

First of all, you’re setting Reverses, DelayTime, and RepeatCount to defaults, so there’s no point in doing that.

Secondly, I’ve just spent 10+ minutes trying to replicate your issue, but I can’t. It’s all behaving fine for me. It might be something else?

Thirdly, when moving the camera, I recommend using RenderStepped instead of Heartbeat. Part of the reason BoatTween exists is so that you can do that!

Finally, you’re using Tweens for that kind of camera, when you should really be using springs. Feel free to DM me if you want help setting that up, because your current method is wasteful and not ideal.

Edit: Resolved in DMs. Showed him the basics of springs and implemented it into his camera system.

1 Like

I have a question! Two of the linked complaints mention lag. Is this module more performant than Roblox’ TweenService because of the event customization? As in Stepped and RenderStepped are less resource intensive than the locked Heartbeat with Roblox’s service?

That’s actually an interesting question, because “lag” here can be applied to two sides of this situation.

Visual lag is when the object moves kinda choppy. TweenService uses Heartbeat, which fires less often, so objects can looks more stuttery due to the lower refresh rate.

Actual lag is when you’re doing framelocked operations that take longer than 16 milliseconds, leading to FPS rendering below 60.

BoatTween allows you to choose between Heartbeat and the Steppeds, so you can remove the visual lag, but if you’re tweening a lot at once then you risk running into actual lag eventually.

BoatTween gives power to the user, allowing you to make the tradeoffs you desire.

3 Likes

W0W. Very interesting module. I’ll definitely experiment with this!

1 Like

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 :slight_smile:

2 Likes

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.

2 Likes

Hi @boatbomber, thank you for sharing your resources!

Would you kindly create a link to Source code (preferably Github and/or Pastebin) for people who want to read the source but don’t have any access to a PC or maybe want to contribute to the source


I also have a feature request:

  • add Repeating & Repeated Event (Tween.Repeat)
  • add Delaying & Delayed event (Tween.Delay)
  • add Reversing & Reversed event (Tween.Reverse)
2 Likes

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

2 Likes

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???

1 Like

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?