Search Unity

Question How to know where(Vector3) the transform will be at the end of the animation?

Discussion in 'Animation' started by Milionario, Oct 22, 2020.

  1. Milionario

    Milionario

    Joined:
    Feb 21, 2014
    Posts:
    138
    So I want to be able to know before playing an animation that applies rootmotion, where will my character be at the end of it?
     
  2. Milionario

    Milionario

    Joined:
    Feb 21, 2014
    Posts:
    138
  3. 00christian00

    00christian00

    Joined:
    Jul 22, 2012
    Posts:
    1,035
    I don't think it's possible.
    You could try backing up the current animator state, use Animator.StartPlayback to manually play the animator, then animator.play to play the animation at normalized time 1(the end), record the position and then restore everything to normal.
    It's not super simple and not sure it will work.
    Usually people do the opposite, decide where they want to go and interpolate the rootmotion to get to desired destination.
     
  4. Havie

    Havie

    Joined:
    Oct 12, 2019
    Posts:
    89
    You could also try adding OnAnimatorMove() and caching Animator.deltaPosition .
    then doing whatever calculation you need here before applying it.
     
  5. 00christian00

    00christian00

    Joined:
    Jul 22, 2012
    Posts:
    1,035
    Animator.deltaPosition only tell you the difference between the current frame and the next frame, but he want to know where it will be at the end of the animation, meaning by using that method he'll only know after the animation has actually finished.
     
    Last edited: Oct 29, 2020
  6. Holonet

    Holonet

    Joined:
    Aug 14, 2017
    Posts:
    84
    I don't think Havie was saying otherwise there, but the point would be the calculation you could derive from doing that. If you perform an animation, you log the start and end positions...taking the difference between them would give you a transform you could add prior.

    I'm imagining there are a few ways to mess that up, such as if the animation has an exit time, performs differently if interrupted, etc... Depends on the application the OP had in mind, and type of animating that's going on.
     
  7. SenseEater

    SenseEater

    Joined:
    Nov 28, 2014
    Posts:
    84
    First you extract the root motion curves from your clips. Then you can simply evaluate those Curves at runtime with clip length to find the translation at end of Clip ( or any time through the clip's length )

    Luckily i have some code lying around from when i was working on something similar.

    We will have to use Editor Script to extract Root Motion Animation Curves for Clips as they are not accessible at runtime and then store into ScriptableObject for easy access. Following is a Scriptable object with both the Extraction and Storage functionality.

    1. Create RootMotionConfiguration.cs script in your project and add the following code.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEditor;
    5. using System;
    6.  
    7. [CreateAssetMenu(fileName = "root_motion_library", menuName = "Game/Animation/Root Motion")]
    8. public class RootMotionConfiguration : ScriptableObject
    9. {
    10.     [Header("Input")]
    11.     public RootMotionCurveNames[] names;
    12.     public AnimationClip[] clips;
    13.  
    14.     [Header("Output")]
    15.     public List<RootMotionCurves> curves = new List<RootMotionCurves>();
    16.  
    17.     Dictionary<AnimationClip, RootMotionCurves> curveDirectory = new Dictionary<AnimationClip, RootMotionCurves>();
    18.  
    19.     [Serializable]
    20.     public struct RotationCurve
    21.     {
    22.         public AnimationCurve x;
    23.         public AnimationCurve y;
    24.         public AnimationCurve z;
    25.         public AnimationCurve w;
    26.     }
    27.  
    28.     [Serializable]
    29.     public struct TranslationCurve
    30.     {
    31.         public AnimationCurve x;
    32.         public AnimationCurve y;
    33.         public AnimationCurve z;
    34.     }
    35.  
    36.     [Serializable]
    37.     public struct RootMotionCurves
    38.     {
    39.         public AnimationClip clip;
    40.         public RotationCurve rotationCurve;
    41.         public TranslationCurve translationCurve;
    42.     }
    43.  
    44.     [Serializable]
    45.     public struct RootMotionCurveNames
    46.     {
    47.         // public string root;
    48.         public PositionCurveName positionCurveName;
    49.         public RotationCurveName rotationCurveName;
    50.     }
    51.  
    52.     [Serializable]
    53.     public struct PositionCurveName
    54.     {
    55.         public string x;
    56.         public string y;
    57.         public string z;
    58.     }
    59.  
    60.     [Serializable]
    61.     public struct RotationCurveName
    62.     {
    63.         public string x;
    64.         public string y;
    65.         public string z;
    66.         public string w;
    67.     }
    68.  
    69.     public RootMotionCurves GetRootMotionCurveForClip(AnimationClip clip)
    70.     {
    71.         foreach (var curve in curves)
    72.             if (curve.clip == clip)
    73.                 return curve;
    74.  
    75.         return default(RootMotionCurves);
    76.     }
    77.  
    78.     [ContextMenu("Process")]
    79.     public void Process()
    80.     {
    81.         curves.Clear();
    82.         curveDirectory.Clear();
    83.         // propertyNames.Clear();
    84.  
    85.         foreach (var clip in clips)
    86.         {
    87.             var curveBindings = UnityEditor.AnimationUtility.GetCurveBindings(clip);
    88.  
    89.             foreach (var name in names)
    90.             {
    91.                 foreach (var curveBinding in curveBindings)
    92.                 {
    93.  
    94.                     if (name.positionCurveName.x == curveBinding.propertyName)
    95.                     {
    96.                         var curve = default(RootMotionCurves);
    97.  
    98.                         if (!curveDirectory.TryGetValue(clip, out curve))
    99.                             curveDirectory.Add(clip, curve);
    100.  
    101.                         var positionCurves = curve.translationCurve;
    102.                         positionCurves.x = AnimationUtility.GetEditorCurve(clip, curveBinding);
    103.                         curve.translationCurve = positionCurves;
    104.                         curveDirectory[clip] = curve;
    105.                     }
    106.  
    107.                     if (name.positionCurveName.y == curveBinding.propertyName)
    108.                     {
    109.                         var curve = default(RootMotionCurves);
    110.  
    111.                         if (!curveDirectory.TryGetValue(clip, out curve))
    112.                             curveDirectory.Add(clip, curve);
    113.  
    114.                         var positionCurves = curve.translationCurve;
    115.                         positionCurves.y = AnimationUtility.GetEditorCurve(clip, curveBinding);
    116.                         curve.translationCurve = positionCurves;
    117.                         curveDirectory[clip] = curve;
    118.                     }
    119.  
    120.                     if (name.positionCurveName.z == curveBinding.propertyName)
    121.                     {
    122.                         var curve = default(RootMotionCurves);
    123.  
    124.                         if (!curveDirectory.TryGetValue(clip, out curve))
    125.                             curveDirectory.Add(clip, curve);
    126.  
    127.                         var positionCurves = curve.translationCurve;
    128.                         positionCurves.z = AnimationUtility.GetEditorCurve(clip, curveBinding);
    129.                         curve.translationCurve = positionCurves;
    130.                         curveDirectory[clip] = curve;
    131.                     }
    132.  
    133.                     if (name.rotationCurveName.x == curveBinding.propertyName)
    134.                     {
    135.                         var curve = default(RootMotionCurves);
    136.  
    137.                         if (!curveDirectory.TryGetValue(clip, out curve))
    138.                             curveDirectory.Add(clip, curve);
    139.  
    140.                         var rotationCurve = curve.rotationCurve;
    141.                         rotationCurve.x = AnimationUtility.GetEditorCurve(clip, curveBinding);
    142.                         curve.rotationCurve = rotationCurve;
    143.                         curveDirectory[clip] = curve;
    144.                     }
    145.  
    146.                     if (name.rotationCurveName.y == curveBinding.propertyName)
    147.                     {
    148.                         var curve = default(RootMotionCurves);
    149.  
    150.                         if (!curveDirectory.TryGetValue(clip, out curve))
    151.                             curveDirectory.Add(clip, curve);
    152.  
    153.                         var rotationCurve = curve.rotationCurve;
    154.                         rotationCurve.y = AnimationUtility.GetEditorCurve(clip, curveBinding);
    155.                         curve.rotationCurve = rotationCurve;
    156.                         curveDirectory[clip] = curve;
    157.                     }
    158.  
    159.                     if (name.rotationCurveName.z == curveBinding.propertyName)
    160.                     {
    161.                         var curve = default(RootMotionCurves);
    162.  
    163.                         if (!curveDirectory.TryGetValue(clip, out curve))
    164.                             curveDirectory.Add(clip, curve);
    165.  
    166.                         var rotationCurve = curve.rotationCurve;
    167.                         rotationCurve.z = AnimationUtility.GetEditorCurve(clip, curveBinding);
    168.                         curve.rotationCurve = rotationCurve;
    169.                         curveDirectory[clip] = curve;
    170.                     }
    171.  
    172.                     if (name.rotationCurveName.w == curveBinding.propertyName)
    173.                     {
    174.                         var curve = default(RootMotionCurves);
    175.  
    176.                         if (!curveDirectory.TryGetValue(clip, out curve))
    177.                             curveDirectory.Add(clip, curve);
    178.  
    179.                         var rotationCurve = curve.rotationCurve;
    180.                         rotationCurve.w = AnimationUtility.GetEditorCurve(clip, curveBinding);
    181.                         curve.rotationCurve = rotationCurve;
    182.                         curveDirectory[clip] = curve;
    183.                     }
    184.                 }
    185.             }
    186.         }
    187.  
    188.         foreach (var clip in curveDirectory.Keys)
    189.         {
    190.             var curve = curveDirectory[clip];
    191.             curve.clip = clip;
    192.             curves.Add(curve);
    193.         }
    194.     }
    195. }
    196.  
    197.  


    2. Create RootMotionConfiguration Asset with Context Menu in Project View from Game/Animation/Root Motion




    3. Provide follow data in the inspector
    • Curves : Name of the curves to be extracted. The Root Motion Curves are stored with MotionT ( translation ) & MotionQ ( rotation ) in my clips. Its probably the standard curve names for root motion but you can define different name in Inspector if your Clips have it named differently. You can use Animation Window to inspect your clip curves.
    • Clips : The Animation Clips you want to extract and store root motion curves for.

    4. Right Click empty inspector area on top of asset to bring up the context Menu. Hit Process to generate the root motion curves list for clips.






    5. The generated curves will appear in the Inspector in Output - Curves section , stored in the asset itself.






    6. Now we can reference this root motion curve asset into our target Script and evaluate it at runtime as following

    Code (CSharp):
    1. public void PlotMotion(Transform targetTransform, AnimationClip clip, RootMotionConfiguration rootMotionConfiguration, float normalizedTime) //normalizedTime ; 0f = start , 1f = end
    2.     {
    3.         RootMotionConfiguration.RootMotionCurves curve = rootMotionConfiguration.GetRootMotionCurveForClip(clip);
    4.         float time = clip.length * normalizedTime;
    5.  
    6.         Vector3 translation = new Vector3()
    7.         {
    8.             x = curve.translationCurve.x.Evaluate(time),
    9.             y = curve.translationCurve.y.Evaluate(time),
    10.             z = curve.translationCurve.z.Evaluate(time),
    11.         };
    12.  
    13.         Quaternion rotation = new Quaternion()
    14.         {
    15.             x = curve.rotationCurve.x.Evaluate(time),
    16.             y = curve.rotationCurve.y.Evaluate(time),
    17.             z = curve.rotationCurve.z.Evaluate(time),
    18.             w = curve.rotationCurve.w.Evaluate(time)
    19.         };
    20.  
    21.         //This is your Position and Rotation at target time
    22.         Vector3 targetPosition = targetTransform.position + translation;
    23.         Quaternion targetRotation = targetTransform.rotation * rotation;
    24.     }
    Hope this helps
    Cheers.
     

    Attached Files:

    Last edited: Nov 1, 2020
  8. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Can't you just do animationClip.averageSpeed * animationClip.length?
     
  9. SVAFnemesis_

    SVAFnemesis_

    Joined:
    Jul 15, 2020
    Posts:
    21
    That's such a pro move! I didn't know exctracting an entire curve is possible. I got a similar question not sure if you can help me as well. I'm trying to query the transform vector3 that's being fed to my specific bone within the hierachy (let's say arm_L) from the animator controller on that very specific frame during runtime. How do I do that?
     
    MurphyMurph_21 likes this.
  10. Nathanieljla

    Nathanieljla

    Joined:
    Apr 18, 2014
    Posts:
    97
    If it's a humanoid you could add a trigger/event on the frame you want (via the animation import options) to extract the bone position, then when handling the callback just use Animator.GetBoneTransform. Otherwise, since the bones are exposed in the hierarchy you could just add the bone to a public variable in your component that handles the callback and query it from there.
     
  11. SVAFnemesis_

    SVAFnemesis_

    Joined:
    Jul 15, 2020
    Posts:
    21
    thank you sir I'll look into that. The second solution you mentioned was what I tried to do, unfortunately I can not get the transform after animator controller and before constraint. I think their order priorities are too close for me to look between them.