Changing keyframes from root transform to root bone in an animation.

First off, I don’t know if this will work in every situation. In my case, I changed the source fbx humanoid rig a bit for creating animations off of mixamo and ended up with great animations but also with the following problem. I was always able to fix it that way when the animation was from mixamo. Might work somewhere else as well:

Assume ending up having an animation where all keyframes which are supposed to be set for the root bone of an avatar are instead set to the root transform, and as a consequence of this, the character will always be fixed to a world position while in the animation.

Here is what I do in order to fix the files.

  • Make a backup of the animation.

  • If there are keyframes for the root bone already, which only have a keyframe with Vector3.zero for position and Quaternion.identity for rotation. Delete all these entries entirely.

  • Save the animation and do File → Save Project.

  • Copy the animation somewhere outside the unity project and only edit it from here on out in the copy outside of the project! (Unity could mess up the file in the whole editing process by reimporting).

  • Open up the animation file in a text editor with a find and replace option (Visual Studio works fine).

  • Search this term :“path: \n” and replace it by “path: root\n”. The whitespace in the search strings are important!
    → The “root” refers to your root bone in the replacement. If your root bone transform is named any other way, you have to use that name instead there.

  • At the bottom of the file there is an option “m_HasGenericRootTransform: 1”. Change it to “m_HasGenericRootTransform: 0”

  • Save the animation.

We ain’t done yet!

The next thing is where the magic comes in because the animation does not yet bind the keyframes to the correct transform. You have to find the reference id for your root bone and replace it at the correct position.

Do the following:

  • Create a new animation for your character that only moves and rotates your root bone by any amount. For convenience, it should only be one keyframe!
  • Save the animation and do File → Save Project.
  • Open up the new animation and search for the string “m_ClipBindingConstant:”. You will see something looking like this:
  ...
m_ClipBindingConstant:
    genericBindings:
    - serializedVersion: 2
      path: 385153371
      attribute: 1
      script: {fileID: 0}
      typeID: 4
      customType: 0
      isPPtrCurve: 0
    - serializedVersion: 2
      path: 385153371
      attribute: 4
      script: {fileID: 0}
      typeID: 4
      customType: 4
      isPPtrCurve: 0
...

The entry with “path: ” is your root bone reference id. Copy this number.

  • Go back into your target animation and search again for “m_ClipBindingConstant:”. You will find something similar to this:
...
m_ClipBindingConstant:
    genericBindings:
    - serializedVersion: 2
      path: 0
      attribute: 1
      script: {fileID: 0}
      typeID: 4
      customType: 0
      isPPtrCurve: 0
    - serializedVersion: 2
      path: 0
      attribute: 2
      script: {fileID: 0}
      typeID: 4
      customType: 0
      isPPtrCurve: 0
...

The entries with the string “path: 0” are referencing the root transform at the moment. Replace the 0 by the number you have saved in the last step.

  • Save the file and let unity import the file by putting the file somewhere into the asset folder.

Everything should be replaced now.

By the way, all this is just stuff that I found by comparing data. The term “reference id” that I used is probably nothing any unity dev would understand, or probably isn’t even what it is. These are all just assumptions based on the data that I analyzed and based on that it worked for me in the end.

Just noticed that visual studio occasionally has a problem with searching for the “path: \n”. The goal is to put a root at the end of all paths that do have not set anything on them. notepad++ works here if you set your cursor in the next line and move the cursor backwards by using the left arrow key until "path: " is selected, and then copy. You can then use the line break that is in your clipboard to use for replacement in notepad++.

Turns out the reference is always the same if it has the same name (maybe it also has to be in the same hierarchy level). Here is some code that does the whole thing. Change the two constant strings two your bone name and bone reference if it is not called “root” and is the first child of the object.

#if UNITY_EDITOR
using System.IO;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;

namespace Editor
{
    public static class ChangeRootBone
    {

        private const string ROOT_BONE_NAME = "root";
        private const string ROOT_BONE_ID = "385153371";

        [MenuItem("Assets/Change Root Bone", false, 14)]
        private static void ReverseClip()
        {
            string directoryPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(Selection.activeObject));
            string fileName = Path.GetFileName(AssetDatabase.GetAssetPath(Selection.activeObject));
            string fileExtension = Path.GetExtension(AssetDatabase.GetAssetPath(Selection.activeObject));
            fileName = fileName.Split('.')[0];
            string copiedFilePath = directoryPath + Path.DirectorySeparatorChar + fileName + "_Rooted" + fileExtension;

            AssetDatabase.CopyAsset(AssetDatabase.GetAssetPath(Selection.activeObject), copiedFilePath);

            string fileContent = File.ReadAllText(copiedFilePath);

            fileContent = fileContent.Replace("path: \n", "path: " + ROOT_BONE_NAME + "\n");

            // Replace "m_HasGenericRootTransform: 1" with "m_HasGenericRootTransform: 0"
            fileContent = fileContent.Replace("m_HasGenericRootTransform: 1", "m_HasGenericRootTransform: 0");

            // Replace "path: 0" to "path: 385153371" for the first two occurrences after "m_ClipBindingConstant:"
            Match match = Regex.Match(fileContent, @"m_ClipBindingConstant:(.*?)path:\s0", RegexOptions.Singleline);
            if (match.Success)
            {
                fileContent = Regex.Replace(fileContent, match.Value, match.Value.Replace("path: 0", "path: " + ROOT_BONE_ID + ""), RegexOptions.Singleline);

                // Find the next match
                match = Regex.Match(fileContent, @"m_ClipBindingConstant:(.*?)path:\s0", RegexOptions.Singleline);
                if (match.Success)
                {
                    fileContent = Regex.Replace(fileContent, match.Value, match.Value.Replace("path: 0", "path: " + ROOT_BONE_ID + ""), RegexOptions.Singleline);
                }
            }

            // Write the modified content back into the file
            File.WriteAllText(copiedFilePath, fileContent);

            AssetDatabase.Refresh();

            UnityEngine.Debug.Log("Root bone changed!");
        }

        [MenuItem("Assets/Change Root Bone", true)]
        static bool ReverseClipValidation()
        {
            return Selection.activeObject.GetType() == typeof(AnimationClip);
        }

        public static AnimationClip GetSelectedClip()
        {
            var clips = Selection.GetFiltered(typeof(AnimationClip), SelectionMode.Assets);
            if (clips.Length > 0)
            {
                return clips[0] as AnimationClip;
            }
            return null;
        }

    }
}

#endif

Right click the animation and click on “Change Root Bone” at the top