Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Smoothly make transition into a playable graph from whatever is currently playing

Discussion in 'Animation' started by kaiyum, Jun 19, 2021.

  1. kaiyum

    kaiyum

    Joined:
    Nov 25, 2012
    Posts:
    686
    Hi there,
    Suppose that there is an animator which is currently playing an animator controller. Now I want to smoothly enter into a new playable graph. But if I do that, the transition is not smooth and the character flicks. How to avoid this?

    I know how to transition into an animator controller from a playable, it's just I am struggling 48 hours to make a transition into a graph.
     
  2. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,554
    If you can fade from a Playable Graph to an Animator Controller then you can do the same thing in reverse to fade back.
     
  3. kaiyum

    kaiyum

    Joined:
    Nov 25, 2012
    Posts:
    686
    Let me elaborate on my situation a bit more:

    The setup is that the object will play a sequence of animation and finally fade into an animator controller whenever the client calls "Sequence.Start()". The requirement is: whenever the client calls the "Sequence.Start()" method, the object must smoothly start playing the sequence. We have no way to know what the 'animation clips' will be in the sequence, they are known at runtime(i.e. They might even come from DLC). This is precisely why I am using playable API. The client can modify the animation clip list and call "Sequence.Start()" any time. The expectation is that the updated sequence should not start abruptly. Suppose that the previous sequence was: A-->B-->D-->F and then 'Ctrl_H'. 'Ctrl_'s are animator controllers. Now we have the sequence data as follows: D-->J-->F-->C-->Ctrl_U. Suppose that the object was in the middle of some state inside 'Ctrl_H'. So when the client will start the updated sequence, the object must smoothly transition from whatever pose it is according to the state of 'Ctrl_H' and then make the transition into D. This is the requirement. To solve the requirement, I tried the following without any luck:

    I stored the currently playing graph along with its PlayableOutput. If the playable output is valid(By 'IsValid()') this means, we are currently playing something in the current graph. I take the sourceplayable by 'GetSourcePlayable()' method. Then in the new graph(yes, every time the sequence will be updated, I create a new graph. I destroy the old graph when the transition will be finished), I set the sourceplayable from the previous graph as input of the mixer of the newly created graph. And tried to manipulate the weight value. The added playable is invalid and any weight update to it makes the object to be in T-Pose. So I want a smooth transition from Ctrl_H to D. But I am getting a transition from T-pose to D.
     
  4. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,554
    When you create a playable you give it the graph you want it to exist in. I'm surprised you don't get an error for trying to use that playable in a different graph (especially after the first was destroyed), but I would be very surprised if it actually worked.

    I'm not sure why you are destroying and recreating graphs, but just stop doing that and manage everything in one graph per character instead. It should be much more efficient too.

    Or if you want to just play animations without worrying about all that low level stuff, you should check out Animancer (link in my signature). It has several ways it can play Animator Controllers and blend them with other animations.
     
    kaiyum likes this.
  5. kaiyum

    kaiyum

    Joined:
    Nov 25, 2012
    Posts:
    686
    You are right, everything in one graph per character is the way to go for me. Regarding the Animancer, I will look into it and it looks promising for me.

    Regarding why I needed to create and destroy a graph:

    Basically, I have a thing called the "IAnimationTask" interface which has a method called "Run(Animator anim, Action OnComplete)". And there is a MonoBehaviour called "ActorAnimation". The ActorAnimation has the "IAnimationTask"'s instance which anyone can access and modify. Basically, anyone can get a reference of "ActorAnimation" and run "Animation Task".

    Animation tasks can be anything that works on the animator attached to the ActorAnimation component's game object. One of several implementations of IAnimationTask is "PlayAnimationSequence". Since the tasks can be generated by the child class of ActorAnimator or anybody outside, so the original idea was: the responsible code will create a graph upon creating/modifying the "IAnimationTask" instance of the "ActorAnimation" component. And then blend the newly created graph with the one already playing. On creation of any graph, these all are sent to a central manager called "FAnimationEngine" which can take care of the lifecycle of graphs, blending and destruction, etc. At the time planned this architecture, I did not know that graphs can not be lerped. Then I tried to take a reference of mixer playable of the previous graph and tried to lerp to that. No matter how I try, be it with animation mixer playable or source playable, it always fails. I can never do things among multiple graphs. It looks like a playable graph is designed to do things within it, unlike Components that can inter-reference and do good/nasty stuff among them.

    TLDR: Changing(Graph.Play()) to a playable graph will immediately follow the graph without any transition. This I probably learned the hard way.
     
  6. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,554
    Most of what you're describing sounds like it would be covered by Animancer's Transitions, End Events, and the Sequence Coroutine example (or the
    ClipTransitionSequence
    class).
    Instead of creating a new graph, just put the new stuff in the same graph.
    Code (CSharp):
    1.                     Old Stuff -> ...
    2. Output -> Mixer ->
    3.                     New Stuff -> ...
    Fade the mixer as necessary then when it's done you can destroy the Old Stuff and move New Stuff to input 0 on the Mixer. Though all that destroying and recreating can be costly so you'd likely get better performance by storing and reusing old playables whenever you replay the same thing (which Animancer also handles automatically).
     
    kaiyum likes this.