How do I play CFrame animations?

Hello there, I am trying to make a animation creating game but I need the animations to be uploaded by CFrame animations so I don’t have to use some website or bot to upload the animations.

I have made my animation and used a Roblox plugin called animation convertor to convert my animations to CFrame animations. Now I have a ModuleScript with a bunch of CFrame code stuff in it. How do I make the CFrame animation play on a player?
Here is a screen shot of the code:

Any help is would be wonderful.
Thanks.

2 Likes

You would use tween or lerp function.

You would need to lerp or tweenservice on a motor6D to get the desired effect. If you can give more details please do.

I don’t understand, how do I make the CFrame animation run on a player like a normal animation?
Also the script is a ModuleScript and not a serverside script

To make CFrame/Procedural animations you need keyframes, which are CFrames, you can make a table of keyframes and for loops to loop through the table.

Lerp & Tweens are important to make the animations smooth.

For Lerping, you need a for “interval” loops.
It should look like this:

for i = 0, 1, 0.1 do
	cf = cf:Lerp(cfkeyframe, i)
	task.wait() -- task.wait() is basically RunService.Heartbeat:Wait().
end

Tweening is just using TweenService;

game:GetService("TweenService"):Create(motor, TweenInfo.new(0.5), {Transform/C1/C0 = keyframecf}):Play()

I strongly prefer using Lerp and not Tweens.
I apologize if I didn’t give enough information on how CFrame animations works, but I hope you get the idea of it.

1 Like

Did you end up with any solutions?

Just use TweenService or Roblox’s animation system.

Yo. I don’t post much, but for anyone who wishes to come up with a system like this, I’ll run through it really quick.
I thought the replies under this post were really vague and it took days of research to find any solutions.

The ModuleScript in this post was created with the Animation Converter plugin by samfundev. Click here for the link to the plugin (free),

Why use CFrame animations? Because animations cannot be saved from one place to another if they are owned by separate people. This means they’re needed for any kind of kit with animations, require() scripts, or if the dev wants better control over animations - like updating them in real-time.

Let’s start. It MIGHT NOT be the greatest approach, but it’s a really good place to start from.
Please note that some things may need to be adjusted to work properly, this won’t work on it’s own unless you have a place to both put it and use it. Will need to disable character animations additionally

Create tables for playing keyframes, playing animations, and fading animations. These will be your main sources for the animations

local playingKeyframes = {}
local playing = {}
local fading = {}

-- this is where you put the CFrame animations from the modules. You could add the require(path) or insert manually
local totalAnimations = {}

-- these are useful for comparing priorities. if there is a better way LET ME KNOW!
local priorities = {
	Enum.AnimationPriority.Core,
	Enum.AnimationPriority.Idle,
	Enum.AnimationPriority.Movement,
	Enum.AnimationPriority.Action,
	Enum.AnimationPriority.Action2,
	Enum.AnimationPriority.Action3,
	Enum.AnimationPriority.Action4,
}

local core = {
     -- these are your core animations. return their names as strings here as an array. these will override current animations. dont include animations which just move an arm or any separate part of the body
}
  • I created a table for animation priorities, ordering the highest priority last - useful for comparing weights later on.
  • I created three functions - One to play, stopAll, and stop the animation keyframes from running.
local function play(name, fade)
	task.spawn(function()
		playAnimation(name)
	end)

	fadeTime(name, fade)
end

local function stop(name, fade, keyframes)
	if playing[name] then
		playing[name] = false
                if keyframes then
			local keyframe = getPlayingKeyframe(name)
			if keyframe and not keyframe.fade then
				removeKeyFrame(keyframe)
			end
		end
	end
end

-- BTW, fade used to be used, but I found a better way recently.
local function stopAll(ignore, fade, keyframes)
	for name, value in playing do
		if value and (name ~= ignore) then
			local default = 0.25

			-- stop animation
			stop(name, fade or default, keyframes)
		end
	end
end

Right, now we have the three functions. Next, we would need to call these functions and continue with the next set of functions - managing playing keyframes.

vvv how one would use this

play(“animation name”, 0.25) – fade no longer works, it’s better this way THOUGH
You will still need to create a system to call the character’s animations - it’s not incredibly tough, but if neccessary, I could give an explanation on how that’s done.

  • For managing keyframes - which would be updated every frame with a RenderStepped function, we’ll need to create a few more functions.
  • The main things to takeaway is that there’ll be ONE keyframe running from a playing animation, and that only the highest priority CFrames from a keyframe will be able to play.

BTW read the comments for any extra information, should help with understanding.

-- this function compares the time duration between two playing animation keyframes
local function compareAdded(time0, time1)
	if not time0 then
		return false
	end
	if not time1 then
		return true
	end
	return time0 > time1
end

-- this function returns the index of the animation priority
local function getPriority(priority, time0)
	local priority = table.find(priorities, priority)
	
	if (time0 == false) then
		return 0
	end
	
	return priority
end

-- this compares two priorities to each-other, used for fading
local function comparePriorities(priority, otherPriority, time0, time1, debugName)
	local priority0 = getPriority(priority, time0)
	local priority1 = getPriority(otherPriority, time1)
	
	if priority0 == priority1 then
		return compareAdded(time0, time1)
	end
	
	if debugName then
		print(priority0 > priority1 and `{debugName} is *greater* than the other anim` or `{debugName} is *less* than the other anim`)
	end
	
	return priority0 > priority1 
end

-- i reassigned next here. I don't know if there's a better way, but this basically
-- just finds the next index in a dictionary, compatible with decimal numbers.
-- if there is no index specified, it finds the first index.
local function next(keyframes, index)
	local closestAfter = 100_000
	local first = 100_000
	
	for newIndex, value in keyframes do
		if index and (newIndex > index) and (newIndex < closestAfter) then
			closestAfter = newIndex
		end
		if (newIndex < first) then
			first = newIndex
		end
	end
	
	if not index then
		return first
	end
	
	return closestAfter
end

-- this function gives both currently playing and fading animations
local function getPlayingAndFade()
	local total = {}
	
	-- get the fading animations too
	for name, value in fading do
		if not value then
			continue
		end
		
		total[name] = value
	end
	
	-- get playing animations
	for name, value in playing do
		if not value then
			continue
		end
		
		total[name] = value
	end
	
	return total
end

-- this function obtains all moving parts in a keyframe, kind-of confusing name, but it'll make sense later
-- some of the assignments were changed like the Torso's hierarchy, because it was confusing originally.
local function getKeyframes(keyframe)
	local keyframes = {}

	-- this doesn't really matter because there's only one keyframe in it
	for rootPart, torso in keyframe.keyframe or keyframe do

		-- get torso's keyframes
		for _, frames in torso do
			
			-- get the keyframes in torso!
			for name, value in frames do
				if not name then
					keyframes.Torso = value
					continue
				end

				keyframes[name] = value
			end
		end 
	end 

	return keyframes
end

-- returns any given playing keyframe from the animation name. At all times, there should only be one max.
local function getPlayingKeyframe(name)
	for _, keyframe in playingKeyframes do
		local keyframeName = keyframe.name
		local keyframeData = keyframe.keyframe
		
		if keyframeName == name then
			return keyframeData, keyframe
		end
	end
	
	return nil
end

-- this obtains the properties, e.g. looping, priorities, and keyframes, of an animation
local function getProperties(name)
	local animation = totalAnimations[name]
	if (not name) or (not animation) then
		warn(name, "doesn't exist")
		return {}, false, Enum.AnimationPriority.Core
	end

	-- get animation properties
	local properties = animation.Properties
	local keyframes = animation.Keyframes

	-- get
	local looping = properties.Looping
	local priority = properties.Priority

	-- return
	return keyframes, looping, priority
end

-- this function gives the parts that are ALLOWED to change from a keyframe.
local function comparePlayingToKeyframe(keyframe, priority)
	local playingAndFade = getPlayingAndFade()
	
	local name = keyframe.name
	local start = playing[name]
	
	local playable = {}
	for name, value in getKeyframes(keyframe) do
		local torso = typeof(value) == "CFrame"
		
		local cframe = torso and value or value.CFrame
		if torso then
			name = "Torso"
		end
		
		playable[name] = cframe
	end
	
	-- cycle through playing animations
	for otherName, otherStart in playingAndFade do
		local animation = totalAnimations[otherName]
		if not animation then
			warn(otherName, "doesn't exist")
			continue
		end
		
		if otherName == name then
			continue
		end
		
		local properties = animation.Properties
		local otherPriority = properties.Priority
		
		-- confirm that this animation is > our current one
		local comparison = comparePriorities(priority, otherPriority, start, otherStart)
		if not comparison then
			local newKeyframe, keyFrameData = getPlayingKeyframe(otherName)
			
			if not newKeyframe then
				continue
			end
			
			for otherName, value in getKeyframes(newKeyframe) do
				playable[otherName] = nil
			end
		end
	end
	
	return playable
end

-- ^^^ extent of the other function, it's basically the same thing
local function checkPlayingPriority(name, keyframe)
	local keyframes, looping, priority = getProperties(name)
	local compare = comparePlayingToKeyframe(keyframe, priority)
	
	return compare
end

-- remove the current playing keyframe
local function removeKeyFrame(keyframe)
	local find = table.find(playingKeyframes, keyframe)
	table.remove(playingKeyframes, find)
end

-- play a keyframe
local function playKeyFrame(name, index, fade)
	local keyframes, looping, priority = getProperties(name)
	
	local keyframe = keyframes[index]
	local newIndex = next(keyframes, index)
	local duration = newIndex - index
	
	if duration > 1000 then
		return true
	end
	
	-- create the keyframe data
	local constructor = {
		index = index, 
		duration = fade or duration,
		keyframe = keyframe,
		name = name,
		fade = fade,
	}
	
	-- add a keyframe / wait
	table.insert(playingKeyframes, constructor)
	task.wait(fade or duration)
	
	-- remove the keyframe
	removeKeyFrame(constructor)
end

-- this is where you would end up if you called play().
-- basically, this sets the playing data, gets the animation, and gets properties
-- then, it creates a loop that plays all keyframes in order, and loops IF looped is on
local function playAnimation(name)
	if playing[name] and playing[name]~=999999999 then
		return
	end
	
	-- set playing value
	local identifier = os.clock()
	playing[name] = identifier
	
	local animation = totalAnimations[name]
	local keyframes, looping = getProperties(name)
	
	local index = next(keyframes)
	
	-- create loop / runs once if not looping
	while (playing[name] == identifier) do
		local cycled = playKeyFrame(name, index)
		if cycled then
			if (not looping) then
				break
			end
			
			index = next(keyframes)
		else
			index = next(keyframes, index)
		end
	end
end

-- this function simply plays the first keyframe of an animation.. used for fading. it returns '999999999' is used to find out if the animation is already fading. so that the same animation doesn't overlap with each-other
local function playFirstKeyframe(name, fade, identifier)
	local keyframes, looping, priority = getProperties(name)
	local index = next(keyframes)
	
	if identifier then
		playing[name] = 999999999
	end
	
	playKeyFrame(name, index, fade)
	return playing[name] == 999999999
end

-- seems to fix some bugs with animations - it just does what I described with the core array from up-top
local function disableOtherAnimations(name)
	for n, v in core do
		if name ~= n then
			playing[n] = false
		end
	end
end

Now that we have our functions which handle keyframes. it is time to figure out HOW we would get the motors from our character. Then, we can figure out how we would move them.

-- this gets all motors inside of the character's body and returns them.
local function getMotor6Ds()
	local motor6Ds = {}

        -- vv YOU WILL NEED TO DEFINE CHARACTER earlier on, it is from Player.Character.
	for _, motor6D in character:GetDescendants() do
		if not motor6D:IsA("Motor6D") then
			continue
		end

		table.insert(motor6Ds, motor6D)
	end

	return motor6Ds
end

-- this cycles through all of the character's motors and finds which ones align with the animations which we have defined from up-top
local function getMotor6DFromName(name)
	for _, motor6D in getMotor6Ds() do
		local part1 = motor6D.Part1
		
		if part1 and part1.Name == name then
			return motor6D
		end
	end
	
	return nil
end

Now that we have obtained the character’s motors, we need to next change their transformations. Transformation is the offset CFrame which regular ROBLOX animations use to move certain limbs.
We will bind a function to RunService’s RenderStepped event. There is implementation already for this, and we can use the animation priority of 300 to mimic character movement.

Should note that this system isn’t 100% and there’s still a few issues needing to be worked out. I’ve seen some non-core animations stop after a keyframe is played, but it could be due to my handling elsewhere.

-- Here, we check every frame to update the highest priority motors. We disable other animations apart of the core array so they don't override
RunService:BindToRenderStep("Animation", 300, function(delta)
	local torsoMotor6D = nil
	
	-- play animations
	for _, keyframe in playingKeyframes do
		local name = keyframe.name
		local fade = keyframe.fade or fading[name]
		local duration = fade or keyframe.duration

                -- the animation is no longer being played
		if (not duration) or (not playing[name] and not fading[name]) then
			continue
		end
		
		if core[name] then
			disableOtherAnimations(name)
		end
		
		local playable = checkPlayingPriority(name, keyframe)
		for name, value in playable do
			local motor6D: Motor6D? = getMotor6DFromName(name)
			if not motor6D then
				continue
			end
			
			if name == "Torso" then
				torsoMotor6D = motor6D
			end
			
                         local alpha = 0.2
                        -- Only part i'm not 100% on is this. The idea is to lerp from the delta, so 1/60th of a frame, the amount of distance between the two points to make a smooth transition over the given time. This would be 1/delta*duration, if you were curious. For now, I'm leaving it at 0.2, but it causes animations to appear slow when you have a ton of keyframes. Thanks 
                        -- We lerp the motor's transformation to create a smooth transition!
			motor6D.Transform = motor6D.Transform:Lerp(value, alpha)
		end
	end
end)

Thanks, this should have gone over the most important parts, but if there’s any questions y’all still have, please let me know. It helps a lot!

Also if any of the whitespace looks out of place it’s because editing in script editor and in the devforum is different.

TL:DR: Play animations via a custom keyframes system - whereas, you give information about the current keyframe, you wait the duration of the keyframe, and repeat. Update the motors’ transformations using a RenderStepped bind.

4 Likes