Animation module, works like humanoid anims, but with blocks

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!

3 Likes