Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Animation Playable And Root Motion

Discussion in 'Animation' started by JosephHK, May 12, 2017.

  1. JosephHK

    JosephHK

    Joined:
    Jan 28, 2015
    Posts:
    40


    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Playables;
    3. using UnityEngine.Animations;
    4.  
    5. public class PlayableTest : MonoBehaviour {
    6.  
    7.     public AnimationClip clip1;
    8.     public AnimationClip clip2;
    9.  
    10.     [Range(-2f, 2f)]
    11.     public float time1 = 0f;
    12.     [Range(-2f, 2f)]
    13.     public float time2 = 0f;
    14.  
    15.     private PlayableGraph graph;
    16.     private AnimationClipPlayable clipPlayable1;
    17.     private AnimationClipPlayable clipPlayable2;
    18.  
    19.     void Start () {
    20.         graph = PlayableGraph.Create();
    21.         clipPlayable1 = AnimationClipPlayable.Create(graph, clip1);
    22.         clipPlayable2 = AnimationClipPlayable.Create(graph, clip2);
    23.  
    24.         AnimationLayerMixerPlayable layerMixerPlayable = AnimationLayerMixerPlayable.Create(graph, 0);
    25.         layerMixerPlayable.SetInputCount(2);
    26.         layerMixerPlayable.SetInputWeight(0, 1f);
    27.         layerMixerPlayable.SetInputWeight(1, 0.5f);
    28.         graph.Connect(clipPlayable1, 0, layerMixerPlayable, 0);
    29.         graph.Connect(clipPlayable2, 0, layerMixerPlayable, 1);
    30.  
    31.         AnimationPlayableOutput output = AnimationPlayableOutput.Create(graph, "Animation", GetComponent<Animator>());
    32.         output.SetSourcePlayable(layerMixerPlayable);
    33.  
    34.         graph.Play();
    35.     }
    36.  
    37.     private void Update () {
    38.         clipPlayable1.SetTime(time1);
    39.         clipPlayable2.SetTime(time2);
    40.     }
    41.  
    42. }
    As shown in the youtube video, whenever the time of an AnimationClipPlayable is changed, the root motion will be calculated according to the time difference.

    My problem: Is there anyway to just change the time of an AnimationClipPlayable without root motion calculated?
    For example, I want to reset the run animation, but I don't want to rewind the position of the character.
     
    Last edited: May 12, 2017
  2. JosephHK

    JosephHK

    Joined:
    Jan 28, 2015
    Posts:
    40
    Okay, I found the trick to disable the root motion calculation.

    Code (CSharp):
    1. private void Update () {
    2.         clipPlayable1.SetTime(time1);
    3.         clipPlayable1.SetTime(time1);
    4.  
    5.         clipPlayable2.SetTime(time2);
    6.         clipPlayable2.SetTime(time2);
    7. }
    Just set the time twice.

    But I found another problem.
    If I set the time of the AnimationClipPlayable like this, I will not want the PlayableGraph to play automatically as the graph will be evaluated with deltaTime of Time.deltaTime so that the time of the AnimationClipPlayable will be increased by Time.deltaTime.

    So I decide to evaluate the graph manually.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Playables;
    3. using UnityEngine.Animations;
    4.  
    5. public class PlayableTest : MonoBehaviour {
    6.  
    7.     public AnimationClip clip1;
    8.     public AnimationClip clip2;
    9.     [Range(-2f, 2f)]
    10.     public float time1 = 0f;
    11.     [Range(-2f, 2f)]
    12.     public float time2 = 0f;
    13.  
    14.     private PlayableGraph graph;
    15.     private AnimationClipPlayable clipPlayable1;
    16.     private AnimationClipPlayable clipPlayable2;
    17.  
    18.     void Start () {
    19.         graph = PlayableGraph.Create();
    20.         clipPlayable1 = AnimationClipPlayable.Create(graph, clip1);
    21.         clipPlayable2 = AnimationClipPlayable.Create(graph, clip2);
    22.  
    23.         AnimationLayerMixerPlayable layerMixerPlayable = AnimationLayerMixerPlayable.Create(graph, 0);
    24.         layerMixerPlayable.SetInputCount(2);
    25.         layerMixerPlayable.SetInputWeight(0, 1f);
    26.         layerMixerPlayable.SetInputWeight(1, 0.5f);
    27.         graph.Connect(clipPlayable1, 0, layerMixerPlayable, 0);
    28.         graph.Connect(clipPlayable2, 0, layerMixerPlayable, 1);
    29.  
    30.         AnimationPlayableOutput output = AnimationPlayableOutput.Create(graph, "Animation", GetComponent<Animator>());
    31.         output.SetSourcePlayable(layerMixerPlayable);
    32.  
    33.         graph.Stop();
    34.     }
    35.  
    36.     private void Update () {
    37.         clipPlayable1.SetTime(time1);
    38.         clipPlayable2.SetTime(time2);
    39.  
    40.         graph.Evaluate(0f);
    41.     }
    42.  
    43. }
    44.  
    However, it turns out that in this case of using graph.Evaluate(0f), the root motion is not calculated.
    Is it a bug?
     
  3. AubreyH

    AubreyH

    Joined:
    May 17, 2018
    Posts:
    18
    One thing I've noticed is that you can set a clip's weight to zero, and then change its time, and since it has a contribution of zero to the root motion, it's safe to change time. It's only when your clip is weighted up that setting its time will affect the root motion delta.

    Unfortunately, this means that you can't jump around in an animation without getting a pop. Definitely irritating. The only way to jump continuously between animation points would be to have another AnimationClipPlayable instance... change its time, then weight it in, and weight out the old one. Hacky but should work.
     
  4. AubreyH

    AubreyH

    Joined:
    May 17, 2018
    Posts:
    18
    I found that instead of setting a time twice to the same value, you want to set twice, but once with the relative previous time to what you want to set it to.

    In other words, when you set the time, fool the clip into thinking it had been at that position the whole time. Don't let it know that it's time travelling, or it will take that time travel into account when calculating root motion.

    i.e. don't do
    Code (CSharp):
    1. playable.SetTime(newTime);
    2. playable.SetTime(newTime);

    instead try

    Code (CSharp):
    1. playable.SetTime(newTime - Time.deltaTime);
    2. playable.SetTime(newTime );
    So. Root motion seems to work by comparing the previous time of the clip to the current time of the clip, and using that to figure out the change in root motion position.

    By setting it to the same thing, you are essentially saying NewRootPos - OldRootPos = Zero
    By putting the head temporarily where it would have been last frame vs. your new time, you give it the ability to say NewRootPos - FakeOldPos = Non mad root change.

    This won't be a catch all solution. In some nodes I set clip time every time in order to have synchronized animation playbacks. In those cases I have to be careful about what my "fake last time" is vis a vis my arbitrarily changing playback rate.
     
    forestrf and y_shichida_asobimo like this.