How are CFrames in KeyframeSequences determined?

I want to know how CFrames are determined in KeyframeSequences because I am trying to make a plugin in which I can convert KeyframeSequences to ModuleScripts with a custom rig. When the Module is executed, the animation appears wrong. The timing is right (I think). The way the rig bones tween is different to what the KeyframeSequence offers in the built-in animation editor.

The plugin’s source:
(By the way, the comments are deleted code that might need to be restored)

function ThisParent(TargetParent, TargetString)
	wait(0.01)
	if TargetParent.Parent ~= game then
		TargetString = "[\""..TargetParent.Name.."\"]"..TargetString
		wait(0.01)
		local ReturnedValue = ThisParent(TargetParent.Parent, TargetString)
		return ReturnedValue
	else
		TargetString = TargetParent.Name..TargetString
		--print(TargetString)
		return TargetString
	end
end
function GetPath(Object)
	return ThisParent(Object, "")
end
script.Parent.Activated:Connect(function()
	local Selection = game:GetService("Selection")
	local KeyframeSequence = Selection:Get()[1]
	if KeyframeSequence.ClassName == "KeyframeSequence" then
		local NewScriptSource = [[
		TweenService = game:GetService("TweenService")
		local Minisched = require(script.Minisched)
		Rig = game.ReplicatedStorage["]]..KeyframeSequence.Name..[[ Rig Value"].Value
		game.ReplicatedStorage["]]..KeyframeSequence.Name..[[ Playing Value"].Changed:Connect(function(Playing)
			if Playing then
				script.Stop.Value = true
				local Animation = coroutine.create(function()
					script.Minisched.Yield.Changed:Connect(function(Property)
						if Property == "Value" and script.Minisched.Yield.Value == true then
							coroutine.yield()
						end
					end)
					[*START REPLACE*]
					while true do
						local Success, Errors = pcall(function()
							[*REPLACE*]
						end)
						if not Success then
							warn(Errors)
							wait(1)
						end
					end
				end)
				script.Stop.Value = true
					wait(0.01)
					script.Stop.Value = false
					local AnimTask = Minisched:Schedule(Animation, tick() + 0.01)
					script.Stop.Changed:Connect(function(Value)
						if Value == true then
							script.Stop.Value = false
							Minisched:Clear()
						end
					end)
			elseif not Playing then
				script.Stop.Value = true
			end
		end)
	]]
		local AnimationSource = ""
		local FirstKeyframeSource = ""
		local Keyframes = KeyframeSequence:GetChildren()
		table.sort(Keyframes, function(A, B)
			return A.Time < B.Time
		end)
		local PreviousPoseStartTime = 0
		local PreviousPoseEndTime = 0
		local PreviousPoseLength = 0
		local PreviousPoseTimes = {}
		local KeyframeOrder = {}
		for Index, AKeyframe in pairs(Keyframes) do
			for Index2, APose in pairs(AKeyframe:GetDescendants()) do
				local PosePath = tostring(string.sub(GetPath(APose), string.len(GetPath(AKeyframe)) + 1, -1))
				if PreviousPoseTimes[PosePath] == nil then
					PreviousPoseTimes[PosePath] = AKeyframe.Time
					table.insert(KeyframeOrder, {
						StartTime = 0,
						EndTime = AKeyframe.Time,
						Style = tostring(string.gsub(tostring(APose.EasingStyle), "Enum%.PoseEasingStyle%.", "")),
						Direction = tostring(string.gsub(tostring(APose.EasingDirection), "Enum%.PoseEasingDirection%.", "")),
						Path = PosePath,
						Goal = APose.CFrame
					})
				else
					table.insert(KeyframeOrder, {
						StartTime = PreviousPoseTimes[PosePath],
						EndTime = AKeyframe.Time,
						Style = tostring(string.gsub(tostring(APose.EasingStyle), "Enum%.PoseEasingStyle%.", "")),
						Direction = tostring(string.gsub(tostring(APose.EasingDirection), "Enum%.PoseEasingDirection%.", "")),
						Path = PosePath,
						Goal = APose.CFrame
					})
					PreviousPoseTimes[PosePath] = AKeyframe.Time
				end
			end
		end
		table.sort(KeyframeOrder, function(A, B)
			return A.StartTime < B.StartTime
		end)
		local PoseCount = 0
		for Index, APose in pairs(KeyframeOrder) do
			PoseCount = PoseCount + 1
		end
		local KeyframeSequenceLength = 0
		for Index, AKeyframe in pairs(Keyframes) do
			if AKeyframe.Time > KeyframeSequenceLength then
				KeyframeSequenceLength = AKeyframe.Time
			end
		end
		local TimeLeft = KeyframeSequenceLength
		for Index, APose in pairs(KeyframeOrder) do
			local KeyframeSource = ""
			local PoseTweenLength = APose.EndTime - APose.StartTime
			--local PosePath = "Rig[\""..tostring(string.gsub(tostring(string.gsub(APose:GetFullName(), AKeyframe:GetFullName()..".", "")), "%.", "\"][\"")).."\"]"
			--print(APose:GetFullName(), AKeyframe:GetFullName())
			--local PrePosePath = tostring(string.gsub(APose:GetFullName(), AKeyframe:GetFullName()..".", ""))
			--print(PrePosePath)
			--print(tostring(string.gsub(PrePosePath, ".", "\"][\"")).."\"]")
			--print(PosePath)
			--local PosePath = "Rig"..tostring(string.sub(GetPath(APose), string.len(GetPath(AKeyframe)) + 1, -1))
			local PosePath = "Rig"..APose.Path
			--print("APose: "..GetPath(APose).."\nAKeyframe: "..GetPath(AKeyframe))
			--print("Final: Rig"..tostring(string.sub(GetPath(APose), string.len(GetPath(AKeyframe)) + 1, -1)))
			local ID = tostring(Index)..tostring(Index)
			if APose.StartTime > PreviousPoseStartTime then
				KeyframeSource = KeyframeSource..[[
		Minisched:Wait(]]..APose.StartTime - PreviousPoseStartTime..[[)
		]]
				TimeLeft = TimeLeft - (APose.StartTime - PreviousPoseStartTime)
			end
			KeyframeSource = KeyframeSource..[[
				pcall(function()
					coroutine.resume(coroutine.create(function()
						TweenService:Create(]]..PosePath..[[, TweenInfo.new(]]..PoseTweenLength..[[, Enum.EasingStyle.]]..APose.Style..[[, Enum.EasingDirection.]]..APose.Direction..[[), {
							Transform = CFrame.new(]]..tostring(APose.Goal)..[[)
						}):Play()
					end))
				end)
			]]
			if APose.EndTime == 0 then
				FirstKeyframeSource = FirstKeyframeSource..[[
				pcall(function()
					coroutine.resume(coroutine.create(function()
						TweenService:Create(]]..PosePath..[[, TweenInfo.new(0.1, Enum.EasingStyle.]]..APose.Style..[[, Enum.EasingDirection.]]..APose.Direction..[[), {
							Transform = CFrame.new(]]..tostring(APose.Goal)..[[)
						}):Play()
					end))
				end)
			]]
			end
			if Index == PoseCount then
				KeyframeSource = KeyframeSource..[[
		Minisched:Wait(]]..TimeLeft..[[)
		]]
			end
			AnimationSource = AnimationSource..[[
		
		]]..KeyframeSource
			PreviousPoseStartTime = APose.StartTime
			PreviousPoseEndTime = APose.EndTime
			PreviousPoseLength = PoseTweenLength
		end
		FirstKeyframeSource = FirstKeyframeSource..[[
		Minisched:Wait(0.1)
		]]
		NewScriptSource = tostring(string.gsub(NewScriptSource, "%[%*START REPLACE%*%]", FirstKeyframeSource))
		NewScriptSource = tostring(string.gsub(NewScriptSource, "%[%*REPLACE%*%]", AnimationSource))
		print(NewScriptSource)
		local NewScript = Instance.new("LocalScript")
		NewScript.Source = NewScriptSource
		--local NewEvent = Instance.new("RemoteEvent")
		--NewEvent.Name = KeyframeSequence.Name.." Remote Event"
		--NewEvent.Parent = NewScript
		local NewModule = Instance.new("ModuleScript")
		NewModule.Name = KeyframeSequence.Name
		NewModule.Source = [[
		local Module = {}
		function Module.LoadAnimation(Nil, Rig)
			local NewRigValue = Instance.new("ObjectValue")
			NewRigValue.Name = "]]..KeyframeSequence.Name..[[ Rig Value"
			NewRigValue.Value = Rig
			NewRigValue.Parent = game.ReplicatedStorage
			local NewPlayingValue = Instance.new("BoolValue")
			NewPlayingValue.Name = "]]..KeyframeSequence.Name..[[ Playing Value"
			NewPlayingValue.Value = false
			NewPlayingValue.Parent = game.ReplicatedStorage
			for Index, APlayer in pairs(game.Players:GetChildren()) do
				script["]]..KeyframeSequence.Name..[[ Animation Client"]:Clone().Parent = APlayer.PlayerGui
			end
			game.Players.PlayerAdded:Connect(function(APlayer)
				script["]]..KeyframeSequence.Name..[[ Animation Client"]:Clone().Parent = APlayer.PlayerGui
			end)
			local Functions = {}
			function Functions.Play()
				script.IsPlaying.Value = true
				NewPlayingValue.Value = true
			end
			function Functions.Stop()
				script.IsPlaying.Value = false
				NewPlayingValue.Value = false
			end
			return Functions
		end
		return Module
		]]
		NewModule.Parent = game.ServerScriptService
		local NewUI = Instance.new("ScreenGui")
		NewUI.Name = KeyframeSequence.Name.." Animation Client"
		NewUI.Parent = NewModule
		NewScript.Parent = NewUI
		--local NewWaitFunction = Instance.new("BindableFunction")
		--NewWaitFunction.Name = "Wait"
		--NewWaitFunction.Parent = NewScript
		--local NewStatusValue = Instance.new("BoolValue")
		--NewStatusValue.Name = "AnimationStatus"
		--NewStatusValue.Value = true
		--NewStatusValue.Parent = NewWaitFunction
		--local NewWaitModule = Instance.new("ModuleScript")
		--NewWaitModule.Source = [[
		--function Wait(Time)
		----wait(Time)
		--return "Success"
		--end

		--script.Parent.OnInvoke = Wait
		--return "Success"
		--]]
		--NewWaitModule.Parent = NewWaitFunction
		local NewStopValue = Instance.new("BoolValue")
		NewStopValue.Name = "Stop"
		NewStopValue.Value = false
		NewStopValue.Parent = NewScript
		local NewIsPlayingValue = Instance.new("BoolValue")
		NewIsPlayingValue.Name = "IsPlaying"
		NewIsPlayingValue.Value = false
		NewIsPlayingValue.Parent = NewModule
		local MinischedClone = script.Minisched:Clone()
		MinischedClone.Parent = NewScript
		Selection:Set({NewModule})
	end
end)
1 Like