I’ve made a module that handles tweening several objects. For example, I want to tween a text label to disappear, but it has a stroke. I would need to make a tween for two objects with basically the same thing, and it would look bad:
local TS = game:GetService("TweenService")
local textLabel = script.Parent
local stroke = textLabel.UIStroke
local tween = TS:Create(textLabel, TweenInfo.New(2, Enum.EasingStyle.Sine, Enum.EasingDirection.In), {TextTransparency = 1})
local tween2 = TS:Create(stroke, TweenInfo.New(2, Enum.EasingStyle.Sine, Enum.EasingDirection.In), {Transparency = 1})
tween:Play()
tween2:Play()
My module would turn all that into this:
local myModule = require(script.Module)
local textLabel = script.Parent
local stroke = textLabel.UIStroke
local tweens = myModule.Create({
{TextTransparency = 1}, -- Goal for textLabel
{Transparency = 1}, -- Goal for UIStroke
}, "2 sine in", textLabel, stroke) -- (time = 2 easingStyle = sine easingDirection = in), and textLabel and stroke, respectively.
tweens:Play() -- Will play both tweens
The actual module that does that:
--!nocheck
local TS = game:GetService("TweenService")
type goal = {{[string] : any}} | {[string] : any}
type info = string | TweenInfo | {string | TweenInfo}
type tweenObject = {
Tweens : {Tween?},
Objects : {Objects},
Goals : goal?,
Infos : {TweenInfo?} | TweenInfo,
SetGoals : (self : tweenObject, Goals : goal, ...number?) -> (),
SetInfos : (self : tweenObject, Infos : info, ...number?) -> (),
AppendTweens : (self : tweenObject, ...number?) -> (),
Play : (self : tweenObject, ...number?) -> (),
}
type tween = {
Create : (Goals : goal?, Infos : info?, ...Object) -> tweenObject,
}
local tween : tween = {}
tween.__index = tween
local easingStyle = {"sine", "quad", "cubic", "linear", "quint", "exponential", "circular", "back", "quart", "bounce", "elastic"}
local easingDirection = {"out", "in", "inout"}
local booleans = {["true"] = true, ["false"] = false}
local syntax = {
"number",
easingStyle,
easingDirection,
"number",
booleans,
"number",
}
-- Returns a table with removed duplicated values from the specified `table`.
local function filtrate(indexes : {number}) : {number}
local filtrated = {}
for _, index in indexes do
if table.find(filtrated, index) then continue end
table.insert(filtrated, index)
end
return filtrated
end
-- Converts the specified `string` to a `TweenInfo` object.
local function convertToTweenInfo(info : string) : TweenInfo?
local options = string.split(info, " ")
local rawOptions = {}
for pos, option in options do
local currentSyntax = syntax[pos]
if not currentSyntax then return end
local convertion
local value
local syntaxType = typeof(currentSyntax)
local lowered = string.lower(option)
if syntaxType == "string" then
convertion = "number"
value = tonumber(option)
else
local enumType = pos == 2 and Enum.EasingStyle or (pos == 3 and Enum.EasingDirection)
if enumType then
convertion = "enum"
if table.find(currentSyntax, lowered) then
value = enumType[option:gsub("^%l", string.upper):gsub("out", "Out")]
end
else
convertion = "boolean"
for booleanName, booleanValue in currentSyntax do
if lowered == booleanName then
value = booleanValue
break
end
end
end
end
if value == nil then
return warn("[{script.Name}]: Couldn't convert \"{option}\" to `{convertion}`; Correct syntax: \"number easingStyle easingDirection number boolean number\"")
end
table.insert(rawOptions, value)
end
return TweenInfo.new(table.unpack(rawOptions))
end
-- Creates a `tween` for the specified `object` with `goal` and `info`, storing it in a `table` for later use.
local function appendTween(object : Object, goal : {[string] : any}, info : TweenInfo, pos : number, store : {Tween?}) : ()
if object and goal then
store[pos] = TS:Create(object, info, goal)
end
end
-- Iterate through indexes and calls the `func` <strong>function</strong> for each index.
local function iterateThroughIndexes(indexes : {number}, func : (index : number) -> ()) : ()
indexes = filtrate(indexes)
for _, index in indexes do
func(index)
end
end
-- Resets the specified `table` or `string` with the `objects`.
local function reset(t : {any} | string, objects : {Object}) : {any}
local nextGoal = {}
for pos in objects do
nextGoal[pos] = t
end
return nextGoal
end
function tween.Create(Goals, Infos, ...)
local objects = {...}
if #objects == 0 then
return warn("[{script.Name}]: No objects to tween")
end
local self = {}
self.Objects = objects
self.Tweens = {}
self.Goals = {}
self.Infos = TweenInfo.new()
setmetatable(self, tween)
self:SetInfos(Infos)
self:SetGoals(Goals)
self:AppendTweens()
return self
end
function tween:SetGoals(Goals, ...)
if debug.info(2, "s") ~= "Create" then
if not Goals then
return warn("[{script.Name}]: Argument 'Goals' is missing")
end
end
local objects = self.Objects
if ... then
if not self.Goals[1] then
self.Goals = reset(self.Goals, objects)
end
iterateThroughIndexes({...}, function(index)
if index < 1 or index > #objects then return end
self.Goals[index] = Goals
end)
return
end
self.Goals = Goals
end
function tween:SetInfos(Infos, ...)
if debug.info(2, "s") ~= "Create" then
if not Infos then
return warn("[{script.Name}]: Argument 'info' is missing")
end
end
local initializeConversion = function(object : (string | TweenInfo)?) : TweenInfo?
object = object or Infos
if typeof(object) == "string" and string.len(object) > 0 then
return convertToTweenInfo(object)
end
end
local objects = self.Objects
if ... then
if typeof(self.Infos) ~= "table" then
self.Infos = reset(self.Infos, objects)
end
iterateThroughIndexes({...}, function(index)
if index < 1 or index > #objects then return end
local info = self.Infos[index]
if info then
self.Infos[index] = initializeConversion(Infos)
end
end)
else
if typeof(Infos) == "table" then
for pos, info in Infos do
Infos[pos] = initializeConversion(info)
end
else
Infos = initializeConversion()
end
self.Infos = Infos
end
end
function tween:AppendTweens(...)
local objects = self.Objects
local info = self.Infos
local goals = self.Goals
local tweens = self.Tweens
local completed = self.CompletedTweens
local appendConnection = self.AppendConnection
if ... then
iterateThroughIndexes({...}, function(index)
if index < 1 or index > #objects then return end
local object = objects[index]
local goal = goals[index] or goals
appendTween(object, goal, typeof(info) == "table" and info[index] or info, index, tweens)
end)
else
for pos, object in objects do
local goal = goals[pos] or goals
appendTween(object, goal, typeof(info) == "table" and info[pos] or info, pos, tweens)
end
end
end
function tween:Play(...)
local objects = self.Objects
local tweens = self.Tweens
if #tweens == 0 then
return warn("[{script.Name}]: There are no tweens to play")
end
if ... then
iterateThroughIndexes({...}, function(index)
if index < 1 or index > #objects then return end
local tween = tweens[index]
if tween then
tween:Play()
end
end)
else
for pos, object in objects do
local tween = tweens[pos]
if tween then
tween:Play()
end
end
end
end
return tween
Though, is that really necessary? Perhaps I’m just overthinking, and the first case where I use TweenService is more suitable.