Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

Timeline Track For Character Movement

Discussion in 'Timeline' started by Cleverlie, Apr 11, 2019.

  1. Cleverlie

    Cleverlie

    Joined:
    Dec 23, 2013
    Posts:
    201
    Hi everyone, I'll introduce the use case of what I'm trying to accomplish first, and then the question.

    What happens?

    Character movement - doing it on timeline is one of the most tedious tasks to do in Unity, imagine this, you have a cinematic cutscene where a character walks to a certain place, then stops in idle, then rotates and walks to another place, he has to avoid some obstacles, move upstairs, stop, walk again, etc etc.

    currently the only way to do this with out-of-the-box tools is using the animation track in timeline, adding lots and lots of clips of walk, idle, walk-left, strafe, jump, etc etc. blend them nicely, adjust every single offset pos and rotation, if something changes later in one of the first clips, you have to readjust the ENTIRE thing. Ok you get it, probably most of us had to deal with this at one point.

    What's the idea? what solution are you thinking of?

    Creating a custom track that allows you to define a spline path for which the character will walk, (I'm thinking maybe using dreamtecks asset), what I need is now just to set the value of the object/character to whatever I need it to be in the spline, then the custom track will compute the velocity of movement of the object along the spline, and do something like mecanim does with blend trees, where I can blend between idle->walk->run depending on the computed velocity.

    What do I need to start?

    first of all, information on how to implement custom animator tracks, I have created multiple custom tracks to deal with many different things I needed for my projects, but never a track that manipulates animators, moreover, I need something that can manipulate the animator of the character, but that it can blend nicely with the usual animator track, so I can use both, one for the character translation, and the other to execute non-locomotion animation clips (such as throwing a punch)

    Now, I know the only thing that manipulates animators in timeline is the Animator Track itself, and the override tracks, I hope there is a way to create this in the form of a "override track" kind of, so when I need a specific clip to play, it blends and overrides the locomotion animation of the "Spline locomotion Track"

    if anyone can point me in the right direction, this could save dozens of hours for animators and people doing character movement in timeline! we could even add it as an addon/free asset to unity, I think this feature is so needed and elemental it could be in vanilla Unity or at least as part of a goodies asset.

    thanks!
     
  2. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,009
    Cool. Let me try and help here with a few tips. This is tricky, but like you said can save a lot of time.

    It sounds like there are several parts to this:
    - custom animation track - the binding type needs to be an animator and the output needs to be an animation playable output.
    e.g.
    Code (CSharp):
    1.         public override IEnumerable<PlayableBinding> outputs
    2.         {
    3.             get { yield return AnimationPlayableBinding.Create(name, this); }
    4.         }
    5.  
    - use AnimationPlayables for mixing and playing back clips. It sounds like you are trying to create a state machine of sorts, so you will need to create and manage the inputs to the mixer manually.

    - time is a tricky issue, especially since some of the methods for passing the correct time are internal. You can try to use https://docs.unity3d.com/ScriptReference/Playables.PlayableExtensions.SetPropagateSetTime.html to have time automatically transfer from output to input playables if that helps.

    - One option is to use an external tool to generate a single animation clip. The gameObjectRecorder and the recorder package can help here. The GameObject recorder can generate an animation clip in playmode. May be in part or a complete option.

    - OnAnimationMove script (with EditInExecuteMode) can be used to set Root Motion.

    That's all I can think of for now..I hope that helps. Please post progress! :)
     
    Cleverlie likes this.
  3. Cleverlie

    Cleverlie

    Joined:
    Dec 23, 2013
    Posts:
    201
    Hi, thanks for the answer, I will do some pseudo code here to explain better what I'm trying to achieve, because I don't even know if it is possible at all with the current API, also I'm not too deep in playables and timeline, I just know the basics (to create tracks, clips, and mixers that use the serialized data of the clips to do stuff for me), but I don't know the advanced stuff or API's.

    The "Follow Path" custom track consist of the TrackAsset class:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Playables;
    5. using UnityEngine.Timeline;
    6.  
    7. [TrackClipType(typeof(FollowPathAsset))]
    8. [TrackBindingType(typeof(Animator))]
    9. [TrackColor(.21f,.60f,.90f)]
    10. public class FollowPathTrack : TrackAsset
    11. {
    12.  
    13.  
    14.     public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
    15.     {
    16.         return ScriptPlayable<FollowPathMixerBehaviour>.Create(graph, inputCount);
    17.     }
    18. }
    19.  
    then the playableBehaviour (clips)

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Playables;
    5.  
    6. [System.Serializable]
    7. public class FollowPathBehaviour : PlayableBehaviour
    8. {
    9.     // reference to a spline to use as the path to follow, these splines could be
    10.     // custom implementations or maybe taken from some asset to save time, such as Dreamtecks Splines
    11.     public SplinePath Path;
    12.  
    13.     // we can let the user trim the path to start and end at whatever value he wants.
    14.     [Range(0,1)]
    15.     public float PathStart = 0;
    16.     [Range(0, 1)]
    17.     public float PathEnd = 1;
    18. }
    19.  
    then the playableAsset:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Playables;
    5.  
    6. public class FollowPathAsset : PlayableAsset
    7. {
    8.     public FollowPathBehaviour template;
    9.  
    10.     public override Playable CreatePlayable(PlayableGraph graph, GameObject owner)
    11.     {
    12.         var playable = ScriptPlayable<FollowPathBehaviour>.Create(graph, template);
    13.         return playable;
    14.     }
    15. }
    16.  
    and then the main part, the mixer behaviour:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Playables;
    5.  
    6. public class FollowPathMixerBehaviour : PlayableBehaviour
    7. {
    8.  
    9.     public override void ProcessFrame(Playable playable, FrameData info, object playerData)
    10.     {
    11.         // get the animator from the binding to this FollowPath track
    12.         Animator trackBinding = playerData as Animator;
    13.  
    14.         Vector3 finalPosition = Vector3.zero;
    15.         Quaternion finalRotation = Quaternion.identity;
    16.         float finalSpeed = 0;
    17.  
    18.         if (!trackBinding)
    19.             return;
    20.  
    21.         int inputCount = playable.GetInputCount(); //get the number of all clips on this track
    22.  
    23.         for (int i = 0; i < inputCount; i++)
    24.         {
    25.             float inputWeight = playable.GetInputWeight(i);
    26.          
    27.             ScriptPlayable<FollowPathBehaviour> inputPlayable = (ScriptPlayable<FollowPathBehaviour>)playable.GetInput(i);
    28.  
    29.             FollowPathBehaviour input = inputPlayable.GetBehaviour();
    30.  
    31.             // Use the above variables to process each frame of this playable.
    32.  
    33.             // somehow, get the current traversing clip start and end times, so we can interpolate
    34.             // and get a 0-1 value to use with the path
    35.  
    36.             TrackClip currentClip = GetCurrentClip(i); // I need to know how to get this
    37.             double currentTimelineTime = GetCurrentTimelineTime(); // I need to know how to get this
    38.  
    39.             // only compute if we are inside the clip
    40.             if (currentClip.StartTime <= currentTimelineTime && currentTimelineTime <= currentClip.EndTime)
    41.             {
    42.                 float currentClipValue = Mathf.InverseLerp(currentClip.StartTime, currentClip.EndTime, currentTimelineTime);
    43.  
    44.                 // we evaluate the spline path at the current position in the clip, given by a o-1 value
    45.                 EvaluationResult er = input.Path.EvaluateAt(currentClipValue);
    46.  
    47.                 // we get the evaluated position, rotation and speed of the moving obj (ignore if speed doesn't make sense, I'm still thinking ways to compute it, it will be useful
    48.                 // later to blend between idle/walk/run animations.
    49.                 Vector3 pos = er.position;
    50.                 Quaternion rot = er.rotation;
    51.                 float speed = er.speed;
    52.  
    53.                 finalPosition += pos * inputWeight;
    54.  
    55.                 finalRotation = Quaternion.Slerp(finalRotation, rot, inputWeight);
    56.  
    57.                 finalSpeed += speed * inputWeight;
    58.             }
    59.  
    60.         }
    61.  
    62.         //now with the results we position the character
    63.        //I know this is not how RootMotion works, but I'm not very experienced so I just coded what intuitively I want to get done.
    64.         trackBinding.transform.position = finalPosition;
    65.         trackBinding.transform.rotation = finalRotation;
    66.  
    67.         // lets do the "blend tree" part
    68.  
    69.         AnimationClip idleClip = GetIdleClip(); // ignore how I get this, I'll figure out later, probably it could be serialized as part of the FollowPathBehaviour (the clip)
    70.         AnimationClip walkClip = GetWalkClip(); // ignore how I get this, I'll figure out later, probably it could be serialized as part of the FollowPathBehaviour (the clip)
    71.         AnimationClip runClip = GetRunClip(); // ignore how I get this, I'll figure out later, probably it could be serialized as part of the FollowPathBehaviour (the clip)
    72.  
    73.         // here is one of the main things I wanna learn how to do, I know I'm oversimplifying here with this pseudo code, but it's because I have zero idea how to achieve this.
    74.         AnimationBlended finalClip = BlendClips(idleClip, walkClip, runClip, finalSpeed);
    75.  
    76.         trackBinding.OverrideCurrentAnimation(finalClip);
    77.     }
    78. }
    79.  

    please ignore the fact that I'm using overly simplified methods that magically do what I want, the reason is that those are the parts that I'm struggling to get working, and I have very little idea how to solve, the code above is all commented so anyone can have a good grasp of what I'm trying to achieve here, Imagine, with this tool the amounts of headaches people will avoid when animating character locomotion in timeline!
     
  4. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,009
    My suggestions if I understand correctly

    - write the final position/rotation to a monobehaviour on the binding that applies it to the transform in LateUpdate(). That will override whatever the animation is writing, which seems to be the intent.
    - Use an animationmixerplayable for the track mixer. And set the output as mentioned above.
    - for the followpathclips,
    - create a subgraph of FollowPathBehaviour ->AnimationMixerPlayable->(3) AnimationClipPlayables.

    Your code can move to FollowPathBehaviour::prepareFrame, and just configure the 3 animation clip playables time and weight.

    For writing to the final position, each clip could add a weight and you can do a blend there.

    For more information on animation playables, you can start here: https://blogs.unity3d.com/2017/11/28/introducing-the-simple-animation-component/

    I hope that helps. That would be a very useful track.
     
    Cleverlie likes this.
  5. Cleverlie

    Cleverlie

    Joined:
    Dec 23, 2013
    Posts:
    201
    thanks, I'll get more deep into it this weekend and see what I can manage to do!
     
  6. Filhanteraren

    Filhanteraren

    Joined:
    May 14, 2014
    Posts:
    47
    did you manage to work more on this?

    Sounds like something that would be very useful.