Playable API blog post

Hi guys,

We just posted a blog post about the new Playable API.

Take a look and let us know what you think about it

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:

namespace ExtraPlayableExtensions
{
    public static class ExtraPlayableExt
    {
        static public IPlayable GetTypedPlayable(this Playable p)
        {
            // We could test each known Playable type, like this:
            //
            // if(p.IsPlayableOfType<AnimatorControllerPlayable>())
            //    return (AnimatorControllerPlayable) p;
            //
            // but instead we'll do it with reflection so we don't have to list every type.
            MethodInfo cast = p.GetPlayableType().GetMethod("op_Explicit", new Type[] { typeof(Playable) });
            if(cast == null)
                return null;

            return (IPlayable) cast.Invoke(null, new object[] { p });
        }

        void Test(Playable p)
        {
            IPlayable playable = p.GetTypedPlayable();
            if(typeof(AnimatorControllerPlayable).IsInstanceOfType(playable))
            {
                AnimatorControllerPlayable animatorControllerPlayable = (AnimatorControllerPlayable) playable;
                // ...
            }
    }
}

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.

public class Exampler: MonoBehaviour 
{
    public Animator anim;
    public AvatarMask mask;
    public RuntimeAnimatorController animatorController1, animatorController2;
    private PlayableGraph graph;
    void OnEnable()
    {
        graph = PlayableGraph.Create();

        // Tell GraphVisualizerClient about our graph, so it's selectable in the dropdown.
        GraphVisualizerClient.Show(graph, "Graph");

        // Create an AnimationPlayableOutput.
        var animOutput = AnimationPlayableOutput.Create(graph, "AnimOutput", anim);

        // Create a AnimationLayerMixerPlayable.  This is the same Playable used by
        // AnimatorControllers to mix layers.  We'll use it to mix two separate AnimatorControllers.
        AnimationLayerMixerPlayable mixer = AnimationLayerMixerPlayable.Create(graph, 2);

        // Make the AnimationPlayableOutput the source for the AnimationPlayableOutput.
        // I'm not sure why the input port isn't a parameter to SetSourcePlayable like it
        // is with ConnectInput.
        animOutput.SetSourcePlayable(mixer);
        animOutput.SetSourceInputPort(0);

        // Create a playable from the first AnimationController and add it to the mixer.
        int layer = 0;
        {
            AnimatorControllerPlayable animatorControllerPlayable = AnimatorControllerPlayable.Create(graph, animatorController1);
            mixer.ConnectInput(layer, animatorControllerPlayable, 0);
            mixer.SetInputWeight(layer, 1);
        }

        // Add another layer to override animation.
        ++layer;
        {
            AnimatorControllerPlayable animatorControllerPlayable = AnimatorControllerPlayable.Create(graph, animatorController2);
            mixer.ConnectInput(layer, animatorControllerPlayable, 0);
            mixer.SetInputWeight(layer, 1);

            // Not sure why this one's unsigned:
            mixer.SetLayerMaskFromAvatarMask((uint) layer, mask);
        }

        graph.Play();
    }
}
1 Like