Splitting an animation to two different rigs

  1. What do you want to achieve?
    ~ I want to split an animation to play on two separate rigs.

  2. What is the issue?
    ~ This seems to be impossible.

  3. What solutions have you tried so far?
    ~ So very much googling and trial-and-erroring, returning too much error to cope with.

Bear with me sorry, it’s long, I tried a lot of stuff.

I want to make a duo dance, so PlayerA and PlayerB dancing together.

I already achieved this once by animating PlayerA, then going back along noting keyframe times and putting position markers to then go and animate PlayerB separately.

This was tedious as heck, and worked for a quick 30 second simple dance, but now I want to do something more complex. I spent three days already just animating this new dance…

So what I did, was I placed PlayerB rig inside PlayerA rig, deleted PlayerB Humanoid and HumanoidRootPart, and renamed PlayerB body parts to BHead, BUpperTorso, etc, and put in a Motor6D from PlayerA LowerTorso to PlayerB LowerTorso.

This was amazing, because now I could animate both players together so I know everything lines up exactly.

The problem I have now, is that those renamed body parts don’t exist in the actual game player.

So PlayerA is doing their movements as expected, and PlayerB does nothing because BHead and BUpperTorso etc don’t exist.

I tried connecting the two players with a Motor6D joint when the dance script starts, but PlayerB just instantly explodes and resets, no matter what I try. Renaming body parts is technically doable but severely screws up the physics, apparently.

I tried scripting to tell it to play BHead on PlayerB > Head etc.
Apparently not a thing.

I tried mapping the Bhead etc body parts to be renamed to just Head etc, and saving the anim as a new anim in a new rig, since the script can play them both at the same time anyway.
Apparently that’s not actually possible either.

I’ve burned three days making an animation, and another entire day today trying to make this work, and now it’s looking like I’m going to have to scrap the whole thing… I need it for tomorrow night and there’s no way I can redo all the PlayerB anim singularly, it’s too intricate.

Please does Anyone have any idea of any way to make this work?

local function copyAnimationTracks(sourceAnimationId, targetRig, trackNameMapping)
	-- Load the source animation
	local sourceAnimation = Instance.new("Animation")
	sourceAnimation.AnimationId = "rbxassetid://" .. sourceAnimationId

	-- Load the animation properly
	sourceAnimation = Instance.new("Animation")
	sourceAnimation.AnimationId = sourceAnimationId

	-- Load the animation onto a temporary rig to read its keyframes
	local tempRig = Instance.new("Model")
	local humanoid = Instance.new("Humanoid", tempRig)
	local animator = Instance.new("Animator", humanoid)

	-- Play the animation
	local animationTrack = animator:LoadAnimation(sourceAnimation)
	animationTrack:Play()
	animationTrack:Stop()

	-- Create a new animation for the target rig
	local newAnimation = Instance.new("Animation")

	-- Extract keyframes from specific tracks
	local keyframesToCopy = {}
	for _, track in ipairs(animationTrack:GetAnimationTrackStates()) do
		local targetTrackName = trackNameMapping[track.Name]
		if targetTrackName then
			keyframesToCopy[targetTrackName] = track:GetKeyframes()
		end
	end

	-- Apply keyframes to new animation
	local newKeyframes = {}
	for trackName, keyframes in pairs(keyframesToCopy) do
		for _, keyframe in ipairs(keyframes) do
			local newKeyframe = Instance.new("Keyframe")
			newKeyframe.Time = keyframe.Time

			-- Copy keyframe data
			for _, pose in ipairs(keyframe:GetPoses()) do
				local targetTrackName = trackNameMapping[pose.Name]
				if targetTrackName then
					local newPose = Instance.new("Pose")
					newPose.CFrame = pose.CFrame
					newPose.Weight = pose.Weight
					newPose.Name = targetTrackName
					newKeyframe:AddPose(newPose)
				end
			end

			table.insert(newKeyframes, newKeyframe)
		end
	end

	-- Create KeyframeSequence for the new animation
	local keyframeSequence = Instance.new("KeyframeSequence")
	for _, keyframe in ipairs(newKeyframes) do
		keyframe.Parent = keyframeSequence
	end

	-- Save the new animation to workspace for further editing and publishing
	local newAnimationInstance = Instance.new("Animation")
	newAnimationInstance.Name = "NewCopiedAnimation"
	keyframeSequence.Parent = newAnimationInstance
	newAnimationInstance.Parent = workspace

	-- Print message to indicate where the new animation is saved
	print("New animation saved to workspace as NewCopiedAnimation")
end

-- Usage Example:
local sourceAnimationId = 18236808053  -- Replace with your source animation ID
local targetRig = workspace.PlayerBRig -- Replace with your target rig

-- Map source track names to target track names
local trackNameMapping = {
	["BLowerTorso"] = "LowerTorso",
	["BLeftUpperLeg"] = "LeftUpperLeg",
	["BLeftLowerLeg"] = "LeftLowerLeg",
	["BLeftFoot"] = "LeftFoot",
	["BRightUpperLeg"] = "RightUpperLeg",
	["BRightLowerLeg"] = "RightLowerLeg",
	["BRightFoot"] = "RightFoot",
	["BUpperTorso"] = "UpperTorso",
	["BHead"] = "Head",
	["BLeftUpperArm"] = "LeftUpperArm",
	["BLeftLowerArm"] = "LeftLowerArm",
	["BLeftHand"] = "LeftHand",
	["BRightUpperArm"] = "RightUpperArm",
	["BRightLowerArm"] = "RightLowerArm",
	["BRightHand"] = "RightHand",

	-- Add more mappings as needed
}

copyAnimationTracks(sourceAnimationId, targetRig, trackNameMapping)

This is the current code I’m working with for renaming tracks and saving as a new anim to a PlayerB only rig.

But apparently direct extraction is not possible with the Roblox API so Idk why I’m even trying that…

Please help me…

So… You are trying to make it so two characters play the same animation?

Uhm, Yes and no.

Imagine like, a partner dance, prom dance, whatever.

They’re doing the same dance, but when PlayerA moves left, PlayerB moves right, or, PlayerA lifts PlayerB or whatever.

Hmmm that is kind of complicated… I hope someone else can help…

Well, I’ve found A solution, I’ll put it here in case anyone else comes looking for the same thing.

Note: I haven’t actually finished the animating yet, I’ve just done this as a test run so far, but, it seems to be working…

So I’ve made the animation in the DUO rig, and saved it as DuoDance.
I’ve then deleted all the PlayerA tracks, and saved it again as BDance.

I’ve got a normal, single rig named PlayB, opened an animation on that and saved it (blank) as FinalDance.

In the Explorer > ServerStorage, I took all the saved keyframes from BDance, copy/pasted them into PlayBs FinalDance, and went through renaming every single bit from used the below script to rename each bit from BHead, BUpperTorso etc to just Head, UpperTorso and so on.

Went back into animating on the PlayB rig, Reloaded the FinalDance anim, and it all loaded in and played.

It’s not perfect. The base of the animation, the LowerTorso, still relies on the main PlayerA LowerTorso, so PlayerB doesn’t always move around the map where they should, but that shouldn’t be too difficult to reposition, and at least all the fiddly arm/legs/head movements are done.

It’s tedious as heck, but, at least now it can be animated together and split.

UPDATE.

Why manually rename stuff when scripting exists.

So, following the steps above, except instead of renaming manually, take the script below, paste it into (wherever), and make sure you change any bits relevant to your project.
Once you’ve done that, copy/paste your script into the COMMAND BAR, hit return and you’re done!

local function renamePosesRecursively(object, nameMapping)
    for _, descendant in ipairs(object:GetDescendants()) do
        if descendant:IsA("Pose") then
            print("Found Pose: " .. descendant.Name)
            local newName = nameMapping[descendant.Name]
            if newName then
                print("Renaming Pose " .. descendant.Name .. " to " .. newName)
                descendant.Name = newName
            else
                print("No mapping found for Pose: " .. descendant.Name)
            end
        end
    end
end

local function renameKeyframes(folder, nameMapping)
    for _, keyframe in ipairs(folder:GetChildren()) do
        if keyframe:IsA("Keyframe") then
            print("Found Keyframe: " .. keyframe.Name)
            renamePosesRecursively(keyframe, nameMapping)
        else
            print("Non-Keyframe Child: " .. keyframe.ClassName)
        end
    end
end

-- Rewrite this path to match wherever your saves are/however they're named.
local keyframeFolder = game.ServerStorage:WaitForChild("RBX_ANIMSAVES"):WaitForChild("BPlayer"):WaitForChild("FinalDance")

-- Table to map name changes. Change either side to match your names.
local nameMapping = {
    ["BLowerTorso"] = "LowerTorso",
    ["BLeftUpperLeg"] = "LeftUpperLeg",
    ["BLeftLowerLeg"] = "LeftLowerLeg",
    ["BLeftFoot"] = "LeftFoot",
    ["BRightUpperLeg"] = "RightUpperLeg",
    ["BRightLowerLeg"] = "RightLowerLeg",
    ["BRightFoot"] = "RightFoot",
    ["BUpperTorso"] = "UpperTorso",
    ["BHead"] = "Head",
    ["BLeftUpperArm"] = "LeftUpperArm",
    ["BLeftLowerArm"] = "LeftLowerArm",
    ["BLeftHand"] = "LeftHand",
    ["BRightUpperArm"] = "RightUpperArm",
    ["BRightLowerArm"] = "RightLowerArm",
    ["BRightHand"] = "RightHand"
    -- Add more mappings as needed
}

renameKeyframes(keyframeFolder, nameMapping)

print("Keyframes renamed successfully")

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.