hello!
So I saw my module yesterday, the module I posted here…
And one method, animation, I thought I could dedicate an entire module for that, just to make it more advanced.
Because it is better to focus on one thing, and make it good, instead of 5 things at the same time with minimal quality.
So I made an animation module! It has keyframes with property, value. and you can play it on any object.
You can use this to get a bit more out of tweenservice.
I, again, don’t know if this is optimized, because I’m generally not that efficient…
But if it works, it works?
here is the module:
local module = {}
local TS = game:GetService("TweenService")
module.Animation = {}
module.Animation.__index = module.Animation
module.Keyframe = {}
module.Keyframe.__index = module.Keyframe
module.KeyframeCollection = {}
module.KeyframeCollection.__index = module.KeyframeCollection
--[[
summary: Initializes a new instance of the keyframeCollection class with the given arguments.
Keyframes: an array of keyframes that need to be added to the keyframecollection's keyframe collection.
info: the tweeninfo to apply to all keyframes.
returns: KeyframeCollection
]]
function module.KeyframeCollection.new(Keyframes : {any}, info : TweenInfo)
local newCol = {}
setmetatable(newCol, module.KeyframeCollection)
newCol.Keyframes = Keyframes
newCol.Info = info
newCol.Listeners = {}
return newCol
end
--[[
summary: adds a callback to the given keyframe collection.
exe: the callback to add.
returns: the KeyframeCollection the listener got added to.
]]
function module.KeyframeCollection:AddEventListener(exe : (...any) -> ...any)
table.insert(self.Listeners, exe)
return self
end
--[[
summary: Initializes a new instance of the Animation class with the given arguments.
keyframeCollections: any keyframecollections that describe the animation.
reverse: if the animation needs to be reversed after it is finished.
repeatCount: how many times to repeat the animation after one full cycle (normal + reverse)
returns: Animation.
]]
function module.Animation.new(keyframeCollections : {any}, reverse : boolean, repeatCount : number)
local newAnim = {}
setmetatable(newAnim, module.Animation)
newAnim.Keyframes = keyframeCollections
newAnim.Progress = 0
newAnim.Reverse = reverse
newAnim.RepeatCount = repeatCount
newAnim.Stop = false
return newAnim
end
--[[
summary: adds an entire keyframecollection with a single keyframe to the animation.
property: the property that the keyframe will change.
value: the value the keyframe will assign to the property.
info: the tweeninfo to apply to the keyframe.
returns: the newly created KeyframeCollection containing a single, very much alone, keyframe.
]]
function module.Animation:AddSingle(property : string, value : any, info : TweenInfo)
local keyframeCollection = {}
setmetatable(keyframeCollection, module.KeyframeCollection)
local keyframe = {}
setmetatable(keyframe, module.Keyframe)
keyframe.Property = property
keyframe.Value = value
keyframeCollection.Info = info or TweenInfo.new(1, Enum.EasingStyle.Linear, Enum.EasingDirection.In, 0, false, 0)
keyframeCollection.Listeners = {}
keyframeCollection.Keyframes = {keyframe}
table.insert(self.Keyframes, keyframeCollection)
return keyframeCollection
end
--[[
summary: Initializes a new instance of the Keyframe class with the given arguments.
property: the property to look for, and modify.
value: the value to assign to the property.
returns: Keyframe
]]
function module.Keyframe.new(property : string, value : any)
local newKey = {}
setmetatable(newKey, module.Keyframe)
newKey.Property = property
newKey.Value = value
return newKey
end
--[[
summary: copies the given animation.
anim: the animation to copy.
returns: a shallow copy of the animation.
]]
function module.Animation.CopyFrom(anim)
local newCopy = {}
setmetatable(newCopy, module.Animation)
newCopy.Keyframes = table.clone(anim.Keyframes)
newCopy.Reverse = anim.Reverse
newCopy.RepeatCount = anim.RepeatCount
newCopy.Progress = anim.Progress
newCopy.Stop = false
return newCopy
end
--[[
summary: clones the given keyframe
returns: a copy of the keyframe
]]
function module.Keyframe:Clone()
local newKey = {}
setmetatable(newKey, module.Keyframe)
newKey.Property = self.Property
newKey.Value = self.Value
return newKey
end
--[[
summary: copies the given keyframe.
key: the keyframe top copy
returns: a copy of the keyframe.
]]
function module.Keyframe.CopyFrom(key)
local newKey = {}
setmetatable(newKey, module.Keyframe)
newKey.Property = key.Property
newKey.Value = key.Value
return newKey
end
--[[
summary: clones the given keyframecollection
returns: a shallow copy of the keyframecollection. (listeners are not cloned)
]]
function module.KeyframeCollection:Clone()
local newCol = {}
setmetatable(newCol, module.KeyframeCollection)
newCol.Keyframes = table.clone(self.Keyframes)
newCol.Listeners = {}
newCol.Info = self.Info
return newCol
end
--[[
summary: copies the properties of the given keyframecollection. (listeners are not copied)
keycol: the keyframecollection to copy.
returns A shallow copy of the given keyframecollection.
]]
function module.KeyframeCollection.CopyFrom(keycol)
local newCol = {}
setmetatable(newCol, module.KeyframeCollection)
newCol.Keyframes = table.clone(keycol.Keyframes)
newCol.Listeners = {}
newCol.Info = keycol.Info
return newCol
end
--[[
summary: returns a shallow copy of an animation.
returns: a shallow copy of the animation.
]]
function module.Animation:Clone()
local newCopy = {}
setmetatable(newCopy, module.Animation)
newCopy.Keyframes = table.clone(self.Keyframes)
newCopy.Reverse = self.Reverse
newCopy.RepeatCount = self.RepeatCount
newCopy.Progress = self.Progress
newCopy.Stop = false
return newCopy
end
--[[
summary: creates a new keyframecollection object with the given arguments, then appends it to the animation track.
info: the tweeninfo to apply to the keyframes in the collection.
keyframes: the keyframes that need to be inserted into the collection.
returns: nil
]]
function module.Animation:Emplace_Back(info : TweenInfo, keyframes)
local keyframeCol = {}
setmetatable(keyframeCol, module.KeyframeCollection)
keyframeCol.Listeners = {}
keyframeCol.Info = info
keyframeCol.Keyframes = keyframes
table.insert(self.Keyframes, keyframeCol)
end
--[[
summary: creates a new keyframe with the given arguments, then appends it to the keyframecollection.
property: the property the keyframe is changing.
value: the value of that property.
returns: the KeyframeCollection the keyframe got added to.
]]
function module.KeyframeCollection:Emplace_Back(property : string, value : any)
local keyframe = {}
setmetatable(keyframe, module.Keyframe)
keyframe.Property = property
keyframe.Value = value
table.insert(self.Keyframes, keyframe)
return self
end
--[[
summary: inserts a keyframecollection into the animation, at the specified position.
keyframe: the keyframecollection to insert.
index: the position where the keyframecollection will be placed.
returns: nil
]]
function module.Animation:InsertKeyframeCollection(keyframe, index : number)
if index <= 0 then--append
table.insert(self.Keyframes, keyframe)
else--insert
table.insert(self.Keyframes, index, keyframe)
end
end
--[[
summary: removes a keyframecollection at the given position.
index: the position of the keyframecollection that needs to be removed.
returns: nil
]]
function module.Animation:RemoveKeyframeCollection(index : number)
if index <= 0 then--remove last
table.remove(self.Keyframes, #self.Keyframes)
else
table.remove(self.Keyframes, index)
end
end
--[[
summary: returns the keyframecollection at the given index.
index: the position of the keyframecollection.
returns: KeyframeCollection
]]
function module.Animation:GetKeyframeCollection(index : number)
if index <= 0 then return self.Keyframes[#self.Keyframes] end
return self.Keyframes[index]
end
--[[
summary: adds a callback function to the given keyframecollection, the callback will be called right before the keyframecollection is activated.
index: the index of the keyframecollection.
callback: the function to run when the keyframecollection becomes active.
returns: true if it succeeded, otherwise false.
]]
function module.Animation:AddEventListenerAt(index : number, callBack : (...any) -> ...any)
if typeof(callBack) ~= "function" then return false end
local key
if index <= 0 then key = self.Keyframes[#self.Keyframes]
else key = self.Keyframes[index] end
if not key then return false end
key:AddEventListener(callBack)
return true
end
--[[
summary: plays the animation object on the given instance, the object needs to have all of the properties inside of the animation, otherwise this throws a cast exception.
obj: the object to play the animation on.
returns: nil
]]
function module.Animation:Play(obj : Instance)
local arr = {}
table.move(self["Keyframes"], self.Progress, #self["Keyframes"], 1, arr)
self.Stop = false
for i = 0, self.RepeatCount do
_playHelper(self, obj)
if not self.Reverse then break end
local newAnim =module.Animation.ReverseAnim(self)
_playHelper(newAnim, obj)
end
end
--[[
summary: this is a helper function for playing animations, it is private, use Animation:Play() for outside - module operations instead.
anim: the animation to play.
obj: the target object to modify.
returns: nil
]]
function _playHelper(anim, obj)
local arr = {}
table.move(anim["Keyframes"], anim.Progress, #anim["Keyframes"], 1, arr)
for i,v in pairs(arr) do
if anim.Stop then break end
anim.Progress += 1
for int, handler in pairs(v.Listeners) do
if typeof(handler) ~= "function" then continue end
handler()
end
local last = nil
for index, key in pairs(v["Keyframes"]) do
local tsSettings = {}
tsSettings[key["Property"]] = key["Value"]
last = TS:Create(obj, v["Info"], tsSettings)
last:Play()
end
if not last then continue end
last.Completed:Wait()
end
end
--[[
summary: reverses an animation and returns it.
anim: the animation to reverse.
returns: a copy of the given animation, but all of the keyframecollections are reversed.
]]
function module.Animation.ReverseAnim(anim) : {any}
local copy = table.clone(anim)
local arr = copy["Keyframes"]
copy["Progress"] = 1
local i, j = 1, #arr
while i < j do
arr[i], arr[j] = arr[j], arr[i]
i = i + 1
j = j - 1
end
copy["Keyframes"] = arr
return copy
end
--[[
summary: requests the animation be stopped, and resets its progress afterwards.
returns: nil
]]
function module.Animation:Stop()
self.Stop = true
self.Progress = 1
end
--[[
summary: requests the animation be stopped, but does not reset its progress.
returns: nil
]]
function module.Animation:Pause()
self.Stop = true
end
return module
so I’m not going to explain things, because uhh, I included explanation next to each function :3
here is an example usage with a part and random properties that I chose for no reason.
local main = TweenInfo.new(0.5, Enum.EasingStyle.Linear, Enum.EasingDirection.In, 0, false, 0)
local colorAnim = mod.Animation.new({}, true, 3)
colorAnim:AddSingle("Color", Color3.fromRGB(255, 0, 0), main):Emplace_Back("Size", Vector3.new(10, 15, 2)):Emplace_Back("Position", Vector3.new(0, 25, 0))
colorAnim:AddSingle("Color", Color3.fromRGB(255, 170, 0), main):Emplace_Back("Size", Vector3.new(5, 3 , 2)):Emplace_Back("Position", Vector3.new(30, 10, 5))
colorAnim:AddSingle("Color", Color3.fromRGB(255, 255, 0), main):Emplace_Back("Size", Vector3.new(4, 4, 4))
colorAnim:AddSingle("Color", Color3.fromRGB(85, 255, 0), main):AddEventListener(test):Emplace_Back("Size", Vector3.new(1, 1, 1))
colorAnim:AddSingle("Color", Color3.fromRGB(0, 170, 255), main):Emplace_Back("Transparency", 0.7):Emplace_Back("Position", Vector3.new(5, 5, 5))
colorAnim:AddSingle("Color", Color3.fromRGB(0, 0, 255), main)
colorAnim:AddSingle("Color", Color3.fromRGB(170, 0, 255), main):Emplace_Back("Transparency", 0.5)
--this became way more than a color anim :)
colorAnim:Play(workspace.animprt)
(if you get the reference of emplace_back then you are a legend, c++)
here is the animation, it is just random properties so chaotic as hell, but I’m sure you could make a better animation than I haha.
again, this is only my second module ever published… so you can give criticism and opinions.
thanks in advance!