Search Unity

  1. New Unity Live Help updates. Check them out here!

    Dismiss Notice

Unity Playable API blog post

Discussion in 'Animation' started by Mecanim-Dev, Aug 2, 2017.

  1. Mecanim-Dev

    Mecanim-Dev

    Unity Technologies

    Joined:
    Nov 26, 2012
    Posts:
    1,668
  2. NoiseFloorDev

    NoiseFloorDev

    Joined:
    May 13, 2017
    Posts:
    104
    I have an editor UI for Animator to let me seek animations, show active transitions, and so on, filling in some missing functionality in the Animator window. To make this work in general with Playable, I need to be able to get the AnimatorController from the AnimatorControlllerPlayable. I can get at it with reflection with AnimatorControllerPlayable.GetAnimatorControllerInternal, but there should be a public API for this, along with AnimationClipPlayable.GetAnimationClipInternal and friends.

    It would be helpful if the graph could display names of objects instead of just their type. I've hacked it up a bit to to do this (controller, clip and layer names), but it took some reflection and some assumptions about the graph structure.

    PlayableExtensions.GetOutput returns the connected Playable, but there's no GetOutputPort to get the corresponding port, so you have to search for it manually.

    It would be convenient for PlayableExtensions.ConnectInput to return the index it used.

    GetInput, etc. return a "dumb" Playable object. I don't know why they don't return the real type upcast to IPlayable, but here's a helper to do the conversion if anyone needs it:

    Code (csharp):
    1.  
    2. namespace ExtraPlayableExtensions
    3. {
    4.     public static class ExtraPlayableExt
    5.     {
    6.         static public IPlayable GetTypedPlayable(this Playable p)
    7.         {
    8.             // We could test each known Playable type, like this:
    9.             //
    10.             // if(p.IsPlayableOfType<AnimatorControllerPlayable>())
    11.             //    return (AnimatorControllerPlayable) p;
    12.             //
    13.             // but instead we'll do it with reflection so we don't have to list every type.
    14.             MethodInfo cast = p.GetPlayableType().GetMethod("op_Explicit", new Type[] { typeof(Playable) });
    15.             if(cast == null)
    16.                 return null;
    17.  
    18.             return (IPlayable) cast.Invoke(null, new object[] { p });
    19.         }
    20.  
    21.         void Test(Playable p)
    22.         {
    23.             IPlayable playable = p.GetTypedPlayable();
    24.             if(typeof(AnimatorControllerPlayable).IsInstanceOfType(playable))
    25.             {
    26.                 AnimatorControllerPlayable animatorControllerPlayable = (AnimatorControllerPlayable) playable;
    27.                 // ...
    28.             }
    29.     }
    30. }
    31.  

    I was going to ask how to do this since it was mentioned in that post, but since I figured it out and there isn't much sample code available I'll just post it here. This takes two separate AnimatorControllers, and makes one override the other using an AvatarMask. This allows both parts to have separate state engines, so you can have a primary AnimatorController, and then apply a separate AnimationController, eg. just on a character's face. This way, the face controller can have its own set of layers each with their own state engines. They'll be combined and then applied to the final animation as a unit that can be weighted on and off as a whole. That's something you can't do with a single controller, since you can't nest layers.

    Code (csharp):
    1.  
    2. public class Exampler: MonoBehaviour
    3. {
    4.     public Animator anim;
    5.     public AvatarMask mask;
    6.     public RuntimeAnimatorController animatorController1, animatorController2;
    7.     private PlayableGraph graph;
    8.     void OnEnable()
    9.     {
    10.         graph = PlayableGraph.Create();
    11.  
    12.         // Tell GraphVisualizerClient about our graph, so it's selectable in the dropdown.
    13.         GraphVisualizerClient.Show(graph, "Graph");
    14.  
    15.         // Create an AnimationPlayableOutput.
    16.         var animOutput = AnimationPlayableOutput.Create(graph, "AnimOutput", anim);
    17.  
    18.         // Create a AnimationLayerMixerPlayable.  This is the same Playable used by
    19.         // AnimatorControllers to mix layers.  We'll use it to mix two separate AnimatorControllers.
    20.         AnimationLayerMixerPlayable mixer = AnimationLayerMixerPlayable.Create(graph, 2);
    21.  
    22.         // Make the AnimationPlayableOutput the source for the AnimationPlayableOutput.
    23.         // I'm not sure why the input port isn't a parameter to SetSourcePlayable like it
    24.         // is with ConnectInput.
    25.         animOutput.SetSourcePlayable(mixer);
    26.         animOutput.SetSourceInputPort(0);
    27.  
    28.         // Create a playable from the first AnimationController and add it to the mixer.
    29.         int layer = 0;
    30.         {
    31.             AnimatorControllerPlayable animatorControllerPlayable = AnimatorControllerPlayable.Create(graph, animatorController1);
    32.             mixer.ConnectInput(layer, animatorControllerPlayable, 0);
    33.             mixer.SetInputWeight(layer, 1);
    34.         }
    35.  
    36.         // Add another layer to override animation.
    37.         ++layer;
    38.         {
    39.             AnimatorControllerPlayable animatorControllerPlayable = AnimatorControllerPlayable.Create(graph, animatorController2);
    40.             mixer.ConnectInput(layer, animatorControllerPlayable, 0);
    41.             mixer.SetInputWeight(layer, 1);
    42.  
    43.             // Not sure why this one's unsigned:
    44.             mixer.SetLayerMaskFromAvatarMask((uint) layer, mask);
    45.         }
    46.  
    47.         graph.Play();
    48.     }
    49. }
    50.  
     
    hopeful likes this.
unityunity