Animation Sharing between Different Characters with Same Topology in Unity

Pope Kim Feb 4, 2012

So let's say you have multiple characters with different bone lengths, but you just don't have manpower to author different animations for all of them. Can we share animations, instead? Sure, why not, if you can live without transition and scale animations on joints: in other words, you are only gonna use rotations. Think it this way. When you rotate your right elbow inward by 90 degrees, your pose will look same as your shorter friend applying the exactly same rotation on his right elbow. Got it?

image

Image Source: http://answers.unity3d.com/questions/191282/sharing-animations-between-models.html

Okay, so I was searching Unity forum to see if rotation-only animation is officially supported. Didn't look like it. Then next up. Can we somehow trim out non-rotational info from an anim file in the pipeline? I was not able to find the answer again, or even anyone asking. (Sorry, after finishing this post, I actually found someone asking, but noone really answered.)

And the good news is……. -drum rolls- …. I figured it out! :D

So this is what I came up with and it works great: (code shown below)

  1. Make a script called ConvertToRotationOnlyAnim.cs inside of Assets/Editor folder.
  2. Add a menu item invoking this script.
  3. Import your animation into Unity. (doesn't matter where it's from as long as Unity sees it as animation)
  4. Right-click on the imported animation asset and select the menu item we just added at step #2.
  5. In the Script, copy over only the curves which have "m_LocalRotation" as propertyName field.
  6. Now set the new _rot animation clip to your game object's animation component.
  7. Hit play and enjoy… :)

And here is the full source code I wrote for this. Hopefully the comment is self-explanatory:

using UnityEditor;
using UnityEngine;

using System.IO;

public class ConvertToRotationOnlyAnim
{
  [MenuItem("Assets/Convert To Rotation Animation")]
  static void ConvertToRotationAnimation()
  {
    // Get Selected Animation Clip
    AnimationClip sourceClip = Selection.activeObject as AnimationClip;
    if (sourceClip == null)
    {
      Debug.Log("Please select an animation clip");
      return;
    }

    // Rotation only anim clip will have "_rot" post fix at the end
    const string destPostfix = "_rot";

    string sourcePath = AssetDatabase.GetAssetPath(sourceClip);
    string destPath = Path.Combine(Path.GetDirectoryName(sourcePath), sourceClip.name) + destPostfix + ".anim";

    // first try to open existing clip to avoid losing reference to this animation from other meshes that are already using it.
    AnimationClip destClip = AssetDatabase.LoadAssetAtPath(destPath, typeof(AnimationClip)) as AnimationClip;
    if (destClip == null)
    {
      // existing clip not found. Let's create a new one
      Debug.Log("creating a new rotation only animation at " + destPath);
     
      destClip = new AnimationClip();
      destClip.name = sourceClip.name + destPostfix;

      AssetDatabase.CreateAsset(destClip, destPath);
      AssetDatabase.Refresh();

      // and let's load it back, just to make sure it's created?
      destClip = AssetDatabase.LoadAssetAtPath(destPath, typeof(AnimationClip)) as AnimationClip;
    }

    if (destClip == null)
    {
      Debug.Log("cannot create/open the rotation only anim at " + destPath);
      return;
    }

    // clear all the existing curves from destination.
    destClip.ClearCurves();

    // Now copy only rotation curves
    AnimationClipCurveData[] curveDatas = AnimationUtility.GetAllCurves(sourceClip, true);
    foreach (AnimationClipCurveData curveData in curveDatas)
    {
      if (curveData.propertyName.Contains("m_LocalRotation"))
      {
        AnimationUtility.SetEditorCurve(
          destClip,
          curveData.path,
          curveData.type,
          curveData.propertyName,
          curveData.curve
        );
      }
    }

    Debug.Log("Hooray! Coverting to rotation-only anim to " + destClip.name + " is done");
  }
}
Update (Mar 21, 2011):

One anonymous commenter enhanced this by allowing local root bone translation. Without this you won't be able to translate your model :)

Change this line to:

if (curveData.propertyName.Contains("m_LocalRotation"))

this:

if (curveData.propertyName.Contains("m_LocalRotation")
 || (curveData.path.Equals("Bip01 Pelvis")
 && curveData.propertyName.Contains("m_LocalPosition")))

Thanks anonymous!