Search Unity

Custom Animation Curves don't come through PlayableGraph?

Discussion in 'Animation' started by tmcsweeneyML, Jul 19, 2018.

  1. tmcsweeneyML

    tmcsweeneyML

    Joined:
    Aug 13, 2013
    Posts:
    7
    For our characters I'm mixing Playable clips with the default Animation Controller (as per the example here: https://docs.unity3d.com/Manual/Playables-Examples.html) so that I can add in and play custom cutscenes clips at runtime without having to add them all to the AnimatorController in advance.

    During a cutscene my playable graph looks like this:



    It all works and when a cutscene animation plays it overrides the animations coming out of the AnimatorController.

    The problem I have is that there are custom AnimationCurves on clips playing in the body layer and they don't drive the corresponding parameters on the Animator (For example I have a custom curve and parameter named "Alpha") Instead the animator parameters still seem to be getting their values from the AnimatorController animations.
     
  2. CraigMcMullen

    CraigMcMullen

    Joined:
    Jan 9, 2020
    Posts:
    3
    Did you manage to find a solution to this issue? I am having a similar problem myself.
     
  3. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Edit: this post is wrong, see my later post below.

    It's not currently possible. You can either play an Animator Controller using an AnimatorControllerPlayable and access its parameters normally (for animations within that Animator Controller only), or you can serialize the curve separately from the clip. This is one of the biggest drawbacks of the Playables API.

    If you want to serialize the curve separately, take a look at the ExposedCurve class in Animancer (link in my signature). It makes it a bit easier, but it's still not particularly convenient. The source code of that class is included in Animancer Lite so feel free to just take it and adapt it for your system if you don't want to use Animancer.
     
    Last edited: Mar 18, 2024
    hopeful likes this.
  4. CraigMcMullen

    CraigMcMullen

    Joined:
    Jan 9, 2020
    Posts:
    3
    Thanks for this, i'll take a look and see what I can do.

    Appreciated
     
  5. Tim2021

    Tim2021

    Joined:
    Jan 19, 2021
    Posts:
    22
    It is 6 years later and re-stumbled on this thread while trying to solve a very similar problem with animation playing in a Timeline not updating Animator parameters.

    This time I have a solution to read out custom curve values but it requires some hoop jumping.

    We have an AnimationPostProcessor that searches each clip as it is imported for named custom curve bindings targeting the Animator and remaps them to serialized fields on a custom component that site alongside the Animator.

    I don't know the full ins and outs of why this works behind the scenes but now the data in the curves is written to those fields regardless of whether the animation is being played in the Animator or the Timeline, and the targeted component can just directly use it's own fields during LateUpdate() rather than having to call Animator.GetFloat("ParamName").


    The important bit inside OnPostProcessAnimation() is this:


    Code (CSharp):
    1.    
    2. private static void RetargetCurves(AnimationClip clip, Type targetType, Dictionary<string, string> curvePropertyMap) {
    3.  
    4.   EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings(clip);
    5.   Type animatorType = typeof(Animator);
    6.          
    7.   for (int i = 0; i < bindings.Length; i++) {
    8.       EditorCurveBinding binding = bindings[i];
    9.       if (binding.type == animatorType)
    10.       {
    11.           if (curvePropertyMap.TryGetValue(binding.propertyName, out string retargetedPath)) {
    12.               //Get the curve values
    13.                      
    14.               AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, binding);
    15.                      
    16.               // map the curve to the new targetType
    17.               clip.SetCurve("",targetType,retargetedPath,curve);
    18.               // clear the old curve
    19.               clip.SetCurve("",animatorType,binding.propertyName,null);
    20.           }
    21.       }
    22.   }
    23. }
    24.  
    The curvePropertyMap is a dictionary of strings that map Custom Curve names to serialized field names within the targeted Monobehaviour.
     
  6. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    I totally forgot about this thread, turns out it actually is possible to read custom curves in a PlayableGraph in a way similar to what Animator Controllers let you do (i.e. you still can't access the actual curve, but you can get its value at the animation's current time).

    First you use animator.BindStreamProperty(animator.transform, typeof(Animator), nameOfTheTargetCurve); to get a PropertyStreamHandle.

    Then you create an AnimationScriptPlayable to play an animation job like this:

    Code (CSharp):
    1.         public struct Job : IAnimationJob
    2.         {
    3.             public NativeArray<PropertyStreamHandle> properties;
    4.             public NativeArray<float> values;
    5.  
    6.             public void ProcessRootMotion(AnimationStream stream) { }
    7.  
    8.             public void ProcessAnimation(AnimationStream stream)
    9.             {
    10.                 for (int i = properties.Length - 1; i >= 0; i--)
    11.                     values[i] = properties[i].GetFloat(stream);
    12.             }
    13.         }
    It will run every frame and grab the curve's current value which your other scripts can just grab from the values array whenever they need it.

    Animancer Lite includes the source code of my implementation if you want to see the full setup. Its usage is demonstrated in the Uneven Ground example.