Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 13 Next capture
2021 2022 2023
1 capture
13 Jun 22 - 13 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
0
Question by Sovogal · Sep 18, 2021 at 08:01 AM · animationeditorroot motion

Scripting Root Motion Curves

Hey. Been banging my head against the keyboard for a week on this issue. I really hope someone here can help me figure out the missing piece to get perfect scripted root motion.

The issue:

I need to be able to script root motion at editor time for playback of the motions at runtime without relying on root motion being applied in the animator. I'm running into some position and rotation issues.

The setup:

I have a mixamo animation called Sword And Shield Slash using a y-bot model and humanoid avatar rigged from the model. I also have an editor script that bakes root motion from the curve bindings of the animation. The script also allows for running both the baked animation through the animator and an un-baked animation from the AnimationClip.SampleAnimation function.

Illustration:


AnimationClip.SampleAnimation: alt text


Manually moving transform from baked curves and Animator.applyRootMotion == false: alt text


Here are the scripts I'm using in case anyone wants to try this for themselves:

RootMotionBaker.cs

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 
 public class RootMotionBaker : MonoBehaviour
 {
     public AnimationClip Clip;
 
     public Animator Animator;
 
     public AnimationCurve PositionXCurve;
     public AnimationCurve PositionYCurve;
     public AnimationCurve PositionZCurve;
 
     public AnimationCurve RotationXCurve;
     public AnimationCurve RotationYCurve;
     public AnimationCurve RotationZCurve;
     public AnimationCurve RotationWCurve;
 
     public AnimationCurve EulerAnglesXCurve;
     public AnimationCurve EulerAnglesYCurve;
     public AnimationCurve EulerAnglesZCurve;
 
 
     // Start is called before the first frame update
     void Start()
     {
     }
 
     // Update is called once per frame
     void Update()
     {
      
     }
 }

RootMotionBakerEditor.cs

 using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Linq;
 using UnityEditor;
 using UnityEditor.Animations;
 using UnityEngine;
 
 [CustomEditor(typeof(RootMotionBaker))]
 public class RootMotionBakerEditor : Editor
 {
 
     private RootMotionBaker _rootMotionBaker;
     private bool _runningBaked;
     private bool _runningStandard;
     private bool _paused;
     private bool _baking;
     private float _totalTimeRunning;
     private AnimatorController _originalAnimatorController;
     private AnimatorController _replacementAnimatorController;
     private float _timeFactor = .5f;
 
     private void Awake()
     {
         _rootMotionBaker = target as RootMotionBaker;
     }
 
     public override void OnInspectorGUI()
     {
         base.OnInspectorGUI();
 
         //Animator
         var animatorProp = serializedObject.FindProperty("Animator");
         if (animatorProp.objectReferenceValue == null)
         {
             animatorProp.objectReferenceValue = _rootMotionBaker.GetComponent<Animator>();
         }
         else
         {
         }
 
         if (serializedObject.FindProperty("Clip").objectReferenceValue != null)
         {
             if (GUILayout.Button("Bake Root Motion Curves"))
             {
                 var curves = RootMotionFunctions.GetCurves(_rootMotionBaker.Clip);
                 WriteCurves(curves);
             }
 
             GUI.enabled = (_runningBaked || _runningStandard);
 
             if (GUILayout.Button("Pause"))
             {
                 _paused = !_paused;
             }
 
 
             GUI.enabled = true;
 
 
             GUI.enabled = !(_runningBaked || _runningStandard);
 
             if (GUILayout.Button("Run Baked Animation"))
             {
                 _runningBaked = true;
                 _rootMotionBaker.Animator.applyRootMotion = false;
 
                 //Set up animator controller
                 _originalAnimatorController = (AnimatorController)_rootMotionBaker.Animator.runtimeAnimatorController;
 
                 _replacementAnimatorController = new AnimatorController();
                 _replacementAnimatorController.AddLayer("Default");
                 _replacementAnimatorController.layers[0].stateMachine.AddState("Default");
                 _replacementAnimatorController.AddMotion(_rootMotionBaker.Clip, 0);
                 _rootMotionBaker.Animator.runtimeAnimatorController = _replacementAnimatorController;
             }
 
             if (GUILayout.Button("Run Standard Animation"))
             {
                 _runningStandard = true;
             }
 
             GUI.enabled = true;
         }
 
         serializedObject.ApplyModifiedProperties();
     }
 
     private void OnSceneGUI()
     {
         if (!_paused)
         {
             if (_rootMotionBaker.Animator != null && _runningBaked) _rootMotionBaker.Animator.Update(Time.deltaTime);
 
             if (_runningBaked)
             {
                 _rootMotionBaker.Animator.PlayInFixedTime(_rootMotionBaker.Clip.name, 0, _totalTimeRunning);
 
                 Vector3 newPosition = new Vector3(_rootMotionBaker.PositionXCurve.Evaluate(_totalTimeRunning), _rootMotionBaker.PositionYCurve.Evaluate(_totalTimeRunning), _rootMotionBaker.PositionZCurve.Evaluate(_totalTimeRunning));
                 Quaternion newRotation = new Quaternion(_rootMotionBaker.RotationXCurve.Evaluate(_totalTimeRunning), _rootMotionBaker.RotationYCurve.Evaluate(_totalTimeRunning), _rootMotionBaker.RotationZCurve.Evaluate(_totalTimeRunning), _rootMotionBaker.RotationWCurve.Evaluate(_totalTimeRunning));
 
 
                 _rootMotionBaker.Animator.transform.position = newPosition;
                 _rootMotionBaker.Animator.transform.rotation = newRotation;
 
                 _totalTimeRunning += Time.deltaTime * _timeFactor;
                 if (_totalTimeRunning >= _rootMotionBaker.Clip.length)
                 {
                     _runningBaked = false;
                     _totalTimeRunning = 0f;
 
                     _rootMotionBaker.Animator.transform.position -= newPosition;
                     _rootMotionBaker.Animator.transform.rotation *= Quaternion.Inverse(newRotation);
 
                     _rootMotionBaker.Animator.runtimeAnimatorController = _originalAnimatorController;
                 }
 
 
             }
 
             if (_runningStandard)
             {
                 _rootMotionBaker.Clip.SampleAnimation(_rootMotionBaker.Animator.gameObject, _totalTimeRunning);
 
                 _totalTimeRunning += Time.deltaTime * _timeFactor;
                 if (_totalTimeRunning >= _rootMotionBaker.Clip.length)
                 {
                     _runningStandard = false;
                     _totalTimeRunning = 0f;
 
                 }
             }
         }
     }
 
     private void WriteCurves(RootMotionFunctions.RootMotionCurves curves)
     {
         serializedObject.FindProperty("PositionXCurve").animationCurveValue = curves.PositionXCurve;
         serializedObject.FindProperty("PositionYCurve").animationCurveValue = curves.PositionYCurve;
         serializedObject.FindProperty("PositionZCurve").animationCurveValue = curves.PositionZCurve;
         serializedObject.FindProperty("RotationXCurve").animationCurveValue = curves.RotationXCurve;
         serializedObject.FindProperty("RotationYCurve").animationCurveValue = curves.RotationYCurve;
         serializedObject.FindProperty("RotationZCurve").animationCurveValue = curves.RotationZCurve;
         serializedObject.FindProperty("RotationWCurve").animationCurveValue = curves.RotationWCurve;
         serializedObject.FindProperty("EulerAnglesXCurve").animationCurveValue = curves.EulerAnglesXCurve;
         serializedObject.FindProperty("EulerAnglesYCurve").animationCurveValue = curves.EulerAnglesYCurve;
         serializedObject.FindProperty("EulerAnglesZCurve").animationCurveValue = curves.EulerAnglesZCurve;
 
     }
 }

RootMotionFunctions.cs (static helper class):

 using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Linq;
 using UnityEditor;
 using UnityEditor.Animations;
 using UnityEngine;
 
 public static class RootMotionFunctions
 {
     public struct RootMotionCurves
     {
         public AnimationCurve PositionXCurve;
         public AnimationCurve PositionYCurve;
         public AnimationCurve PositionZCurve;
 
         public AnimationCurve RotationXCurve;
         public AnimationCurve RotationYCurve;
         public AnimationCurve RotationZCurve;
         public AnimationCurve RotationWCurve;
 
         public AnimationCurve EulerAnglesXCurve;
         public AnimationCurve EulerAnglesYCurve;
         public AnimationCurve EulerAnglesZCurve;
     }
 
 
     public static RootMotionCurves GetCurves(AnimationClip clip)
     {
         Action<AnimationCurve, float, float> normalizeKeyframes = (curve, minValue, maxValue) =>
         {
             if (curve.keys.Length > 1)
             {
                 Keyframe[] keyframes = curve.keys.ToArray();
                 float startFrameValue = keyframes.First().value;
                 for (int i = 0; i < keyframes.Length; i++)
                 {
                     keyframes[i].value = Mathf.Clamp(keyframes[i].value - startFrameValue, minValue, maxValue);
                 }
                 curve.keys = keyframes;
             }
         };
 
         var positionXCurve = new AnimationCurve { keys = GetCurveFromAnimationClip(clip, "RootT.x").keys.ToArray() };
         normalizeKeyframes(positionXCurve, float.NegativeInfinity, float.PositiveInfinity);
 
         var positionYCurve = new AnimationCurve { keys = GetCurveFromAnimationClip(clip, "RootT.y").keys.ToArray() };
         normalizeKeyframes(positionYCurve, float.NegativeInfinity, float.PositiveInfinity);
 
         var positionZCurve = new AnimationCurve { keys = GetCurveFromAnimationClip(clip, "RootT.z").keys.ToArray() };
         normalizeKeyframes(positionZCurve, float.NegativeInfinity, float.PositiveInfinity);
 
         var quaternionXCurve = new AnimationCurve { keys = GetCurveFromAnimationClip(clip, "RootQ.x").keys.ToArray() };
         var quaternionYCurve = new AnimationCurve { keys = GetCurveFromAnimationClip(clip, "RootQ.y").keys.ToArray() };
         var quaternionZCurve = new AnimationCurve { keys = GetCurveFromAnimationClip(clip, "RootQ.z").keys.ToArray() };
         var quaternionWCurve = new AnimationCurve { keys = GetCurveFromAnimationClip(clip, "RootQ.w").keys.ToArray() };
 
         var eulerAnglesXCurve = new AnimationCurve();
         var eulerAnglesYCurve = new AnimationCurve();
         var eulerAnglesZCurve = new AnimationCurve();
 
         //Resample and normalize quaternions
         var frames = (int)(clip.length * clip.frameRate);
         var quaternionXKeyframes = new Keyframe[frames];
         var quaternionYKeyframes = new Keyframe[frames];
         var quaternionZKeyframes = new Keyframe[frames];
         var quaternionWKeyframes = new Keyframe[frames];
         var eulerAnglesXKeyframes = new Keyframe[frames];
         var eulerAnglesYKeyframes = new Keyframe[frames];
         var eulerAnglesZKeyframes = new Keyframe[frames];
         Quaternion inverseStartRotation = Quaternion.Inverse(new Quaternion(quaternionXCurve.keys[0].value, quaternionYCurve.keys[0].value, quaternionZCurve.keys[0].value, quaternionWCurve.keys[0].value));
         for (int i = 0; i < frames; i++)
         {
             float time = i * 1 / clip.frameRate;
 
             var xValue = quaternionXCurve.Evaluate(time);
             var yValue = quaternionYCurve.Evaluate(time);
             var zValue = quaternionZCurve.Evaluate(time);
             var wValue = quaternionWCurve.Evaluate(time);
 
             Quaternion sample = new Quaternion(xValue, yValue, zValue, wValue);
             var resample = sample * inverseStartRotation;
 
             quaternionXKeyframes[i].time = time;
             quaternionXKeyframes[i].value = resample.x;
             quaternionYKeyframes[i].time = time;
             quaternionYKeyframes[i].value = resample.y;
             quaternionZKeyframes[i].time = time;
             quaternionZKeyframes[i].value = resample.z;
             quaternionWKeyframes[i].time = time;
             quaternionWKeyframes[i].value = resample.w;
 
 
             Vector3 eulerAnglesSample = new Quaternion(xValue, yValue, zValue, wValue).eulerAngles;
             eulerAnglesXKeyframes[i].time = time;
             eulerAnglesXKeyframes[i].value = eulerAnglesSample.x;
             eulerAnglesYKeyframes[i].time = time;
             eulerAnglesYKeyframes[i].value = eulerAnglesSample.y;
             eulerAnglesZKeyframes[i].time = time;
             eulerAnglesZKeyframes[i].value = eulerAnglesSample.z;
 
         }
         quaternionXCurve.keys = quaternionXKeyframes;
         quaternionYCurve.keys = quaternionYKeyframes;
         quaternionZCurve.keys = quaternionZKeyframes;
         quaternionWCurve.keys = quaternionWKeyframes;
 
         eulerAnglesXCurve.keys = eulerAnglesXKeyframes;
         eulerAnglesYCurve.keys = eulerAnglesYKeyframes;
         eulerAnglesZCurve.keys = eulerAnglesZKeyframes;
 
         return new RootMotionCurves
         {
             PositionXCurve = positionXCurve,
             PositionYCurve = positionYCurve,
             PositionZCurve = positionZCurve,
             RotationXCurve = quaternionXCurve,
             RotationYCurve = quaternionYCurve,
             RotationZCurve = quaternionZCurve,
             RotationWCurve = quaternionWCurve,
             EulerAnglesXCurve = eulerAnglesXCurve,
             EulerAnglesYCurve = eulerAnglesYCurve,
             EulerAnglesZCurve = eulerAnglesZCurve,
         };
     }
 
     private static AnimationCurve GetCurveFromAnimationClip(AnimationClip clip, string curvePropertyName)
     {
         var curveBindings = UnityEditor.AnimationUtility.GetCurveBindings(clip);
 
         //var curve = new AnimationCurve { keys = UnityEditor.AnimationUtility.GetEditorCurve(clip, curveBindings.FirstOrDefault(x => x.propertyName == curvePropertyName)).keys };
         var curve = AnimationUtility.GetEditorCurve(clip, curveBindings.FirstOrDefault(x => x.propertyName == curvePropertyName));
 
         return curve;
     }
 }

sampleanimation.png (128.2 kB)
baked.png (134.1 kB)
Comment
Add comment
10 |3000 characters needed characters left characters exceeded
â–¼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

0 Replies

· Add your reply
  • Sort: 

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

341 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

How can I disable root motion during animation play? 1 Answer

How to make Bones visible in Editor Mode ? 5 Answers

Edit anim or FBX file that's linked to Mechanim? Can't? 3 Answers

Animation Preview in Custom Asset Inspector Window 2 Answers

Not importing position animation 0 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges