BoatTween module

Lately, there have been various complaints about TweenService, so I decided to spend an hour or two making an alternative that offers some more.

Introducing… BoatTween!

(Because @SteadyOn already took the name TweenService2)

Before we begin, credit where credit is due. @howmanysmaII gave me various lerping and tweening algorithms, and she credits Fractality for various Lua functions, IBM + Google + Microsoft + Mozilla for the bezier curves, and Validark for rewriting certain equations.

Now, into the module.

Why use this over Roblox’s TweenService?

Customization and power.

This module offers 32 easing styles (compared to Roblox’s 11) and they all have the 3 easing directions as well, allowing you to find exactly the tween timing you desire.

This module allows you to choose what event the tween runs on, so you can use Stepped, RenderStepped, or Heartbeat depending on your needs instead of being locked to Heartbeat with Roblox’s service.

This module allows you to tween nearly any value, including NumberRanges and PhysicalProperties, which makes it far more useful and versatile.

This module has more accurate color lerping than TweenService’s Color3:Lerp() method.

This module has events for Stopped, Completed, and Resumed on Tween objects but Roblox only has Completed which doesn’t differentiate between finishing and stopping.

This module offers all the usual tween features like reversing, repeating, and PlaybackState as well, so there are only gained features, not sacrifices.

Easier API.

It takes a dictionary (instead of a TweenInfo object) so you don’t need to make a new object and remember the parameter order every time you make a tween.

API:

-- BoatTween

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"
			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
		}

-- Tween

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

Example:

local BoatTween = require(script.BoatTween)

local Tween = BoatTween:Create(workspace:WaitForChild("Part"),{
	Time = 4;
	EasingStyle = "EntranceExpressive";
	EasingDirection = "In";
	
	Reverses = true;
	DelayTime = 1;
	RepeatCount = 3;
	
	StepType = "Heartbeat";
	
	Goal = {
		Color = Color3.new(0.5,0.2,0.9);
		Position = Vector3.new(4,10,7);
		Transparency = 0.5;
	};
})
Tween:Play()

Module:


Note: I didn’t test error cases carefully, because if you input invalid types and such that’s just your fault. TweenService would break too. Just don’t tell it to tween a Color3 to a Vector3 or something like that and you’ll be fine.

Remember to :Destroy() your tweens after usage!




Enjoying my work? I love to create and share with the community, for free.

If you’d like to help fund my work, consider sponsoring me on GitHub/Patreon or donating on BuyMeACoffee/PayPal!

283 Likes

Important question. Does it tween boats?

121 Likes

I love the modules that you develop. Thank you for making them!

6 Likes

Quick question; by ‘more accurate colour lerping’ do you mean gamma correct lerping? :open_mouth:

6 Likes

Why yes, yes it does.

This is the laggiest GIF ever made, but I assure you it tweened smoothly.
ezgif.com-video-to-gif

74 Likes

This is my Color3 lerper :upside_down_face:

	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;
28 Likes

Nitpick: Why not custom Enums instead of strings as your parameter EasingStyle and EasingDirection to the function BoatTween.Create?

1 Like

Made it easier to setup the internal dictionaries and makes it easier for people to add their own styles.

3 Likes

Thanks for this. Is there any chance of adding support for custom functions, like Crazyman32’s Tween module? I often have modules that need to be tweened that aren’t instances, so don’t fit neatly within the property model. Looking at your code quickly, it seems you already use functions that take this form internally, so maybe it wouldn’t be that hard to add?
For example,

local props = {
	Time = 2,
	EasingStyle = "Quad",
	EasingDirection = "In",
	StepType = "Heartbeat"
}
-- 'delta' being between 0 and 1
local tween = BoatTween:Create(function(delta)
	customModuleIWantToTween:Update(delta)
end,props)
2 Likes

For now, I’m going to work on other things but I’ll likely come back to this module in the future. That’ll be on my to-do list at low priority.

This module was designed as a TweenService alternative, not a one-size-fits-all tweening module. TweenService is only for Instances, so for now, mine is too.

1 Like

I have a suggestion:
add OutIn (in out but out in)

3 Likes

I was going to, but the functions for it weren’t nearly as clear for the transfer.

SoftSpring is one of the easy ones, it’s quite clear what you have to do:

local function SoftSpring(t, b, c, d)
	t = t / d
	return (1 + (-math.exp(-7.5 * t) * math.cos(-10.053096491487 * t))) * c + b
end

local function SoftSpring(T)
	return 1 + (-math.exp(-7.5 * T) * math.cos(-10.053096491487 * T))
end

Simply remove the t = t / d and the trailing * c + b and it’s converted. Now if we look at OutInQuad, there’s a clearly different thing going on here:

local function OutInQuad(t, b, c, d)
	if t < d / 2 then
		t = 2 * t / d
		return -c / 2 * t * (t - 2) + b
	else
		t = ((t * 2) - d) / d
		c = c / 2
		return c * t * t + b + c
	end
end

I would’ve done it, but I just couldn’t figure it out. It’s up to you if you want to make it yourself, as I’m not really up to the task.

3 Likes

This seems like a great module! I’ll be sure to try it out in the future.

3 Likes

Very cool! Another great boatbomber module

2 Likes

Agree with SteadyOn, want to use this. I have a question: Why you named this BoatTween? Nice Name, but i just am asking me if you chosed this name randomly or have a reason.

2 Likes

I was gonna do “TweenService2” but the name was taken so I slapped Boat onto it

4 Likes

Just a small question, do you have any results or info on how this compares efficiency wise with roblox’s tween service?

4 Likes

Ok, nice. Then Last question (am sure!):
How do you created your Color3 lerper? Ik that i not really am good with Color3, but your code really exceed all what i had in mind.

P.S.: And why you created other EasingStyles?

2 Likes

That question should be directed to @howmanysmaII, since I got it from her.

More options available to developers is always a good thing!

6 Likes

Setting the RepeatCount to 0 makes it so that the tween doesn’t play

1 Like