Hey there,
What am I supposed to do with AnimationClipPlayable now that it has disappeared in 5.6?
Cheers,
Paul
Hey there,
What am I supposed to do with AnimationClipPlayable now that it has disappeared in 5.6?
Cheers,
Paul
Hi.
It has not disappeared, the Playable API has changed in 5.6 and the docs will be updated real soon.
until then, here is the draft of the documentation
Playable API
The Playables API provides a way to create tools, effects or other gameplay mechanisms by organizing and evaluating data sources in a tree-like structure.
This tree-like structure allows you to mix/blend/modify multiple data sources and play them through a single output. In its current form, the Playable API supports animation graphs, providing the capacity to interact with Mecanim via scripting.
Even though the playable api is currently limited to AnimationPlayable and ScriptPlayable, it is a generic API that will in the future be applied to Audio, Video and other systems.
How does it work
The PlayableGraph, Playable, PlayableHandle and PlayableOutput
The PlayableGraph is responsible for lifecycle of the Playables. It it used for creation, destruction and connection of Playables. The PlayableGraph defines a set of PlayableOutputs that are binded to scene object/component. For instance, an AnimationOutput is binded to to an Animator Component.
Generally, when working with the graph, it is preferred to use PlayableHandles. Theses C# struct have the advantage of not allocating GC memory. PlayableHandles are used for all topological operations - connection, disconnection, creation - on the graph. It is always possible to get the Playable object from the PlayableHandle by using the PlayableHandle.GetObject<> method. The Playable.handle method returns the PlayableHandle of the Playable.
Playable Lifetime
Destroying the PlayableGraph will automatically destroy all Playables and PlayableOutputs that were created by the graph. Users must call PlayableGraph.Destroy() manually otherwise Unity will generate an error message.
Examples
Playing a single clip on a GameObject
The following MonoBehaviour creates a simple tree with a single node, which plays a single clip.
A new type of object is introduced in this example: the AnimationClipPlayable, which derives from the generic AnimationPlayable, and wraps an AnimationClip in order to make it compatible with the Playable API.
using UnityEngine;
using UnityEngine.Experimental.Director;
[RequireComponent (typeof (Animator))]
public class PlayAnimation : MonoBehaviour
{
public AnimationClip clip;
PlayableGraph playableGraph;
void Start ()
{
playableGraph = PlayableGraph.CreateGraph();
var playableOutput = playableGraph.CreateAnimationOutput(“Animation”, GetComponent());
// Wrap the clip in a playable
var clipPlayable = playableGraph.CreateAnimationClipPlayable(clip);
// Connect the Playable to an output
playableOutput.sourcePlayable = clipPlayable;
// Plays the Graph.
playableGraph.Play();
}
void OnDisable()
{
// Destroys all Playables and Outputs created by the graph.
playableGraph.Destroy();
}
}
Note that AnimationPlayableUtilities provides a higher level utility functions that simplifies creation and playback of AnimationPlayables.
using UnityEngine;
using UnityEngine.Experimental.Director;
[RequireComponent (typeof (Animator))]
public class PlayAnimation : MonoBehaviour
{
public AnimationClip clip;
PlayableGraph playableGraph;
void Start ()
{
AnimationPlayableUtilities.PlayClip(GetComponent(), clip, out playableGraph);
}
void OnDisable()
{
// Destroys all Playables and Outputs created by the graph.
playableGraph.Destroy();
}
}
Creating an animation blend tree programmatically
We now introduce the AnimationMixerPlayable, which can blend two or more AnimationClipPlayables, or blend other mixers which themselves blend other clips, etc.
The weight of each clip in the blend is adjusted dynamically via SetInputWeight().
using UnityEngine;
using UnityEngine.Experimental.Director;
[RequireComponent (typeof (Animator))]
public class MixAnimation : MonoBehaviour
{
public AnimationClip clip1;
public AnimationClip clip2;
public float weight;
private PlayableGraph playableGraph;
private PlayableHandle mixer;
void Start ()
{
// Creates the graph, the mixer and binds them to the Animator.
mixer = AnimationPlayableUtilities.PlayMixer(GetComponent(), 2, out playableGraph);
// Creates AnimationClipPlayable and connects them to the mixer.
playableGraph.Connect(playableGraph.CreateAnimationClipPlayable(clip1), 0, mixer, 0);
playableGraph.Connect(playableGraph.CreateAnimationClipPlayable(clip2), 0, mixer, 1);
}
void Update()
{
weight = Mathf.Clamp01(weight);
mixer.SetInputWeight(0, 1.0f-weight);
mixer.SetInputWeight(1, weight);
}
void OnDisable()
{
// Destroys all Playables and Outputs created by the graph.
playableGraph.Destroy();
}
}
Blending AnimatorController
Just like the AnimationClipPlayable wraps an AnimationClip, the AnimatorControllerPlayable can wrap an AnimatorController, allowing us to blend them with other AnimationPlayables.
using UnityEngine;
using UnityEngine.Experimental.Director;
[RequireComponent (typeof (Animator))]
public class MixController : MonoBehaviour
{
public AnimationClip clip1;
public RuntimeAnimatorController controller;
public float weight;
private PlayableGraph playableGraph;
private PlayableHandle mixer;
void Start ()
{
// Creates the graph, the mixer and binds them to the Animator.
mixer = AnimationPlayableUtilities.PlayMixer(GetComponent(), 2, out playableGraph);
// Creates and connects the Playbles to the mixer.
playableGraph.Connect(playableGraph.CreateAnimationClipPlayable(clip1), 0, mixer, 0);
playableGraph.Connect(playableGraph.CreateAnimatorControllerPlayable(controller), 0, mixer, 1);
}
void Update()
{
weight = Mathf.Clamp01(weight);
mixer.SetInputWeight(0, 1.0f-weight);
mixer.SetInputWeight(1, weight);
}
void OnDisable()
{
// Destroys all Playables and Outputs created by the graph.
playableGraph.Destroy();
}
}
Controlling the timing of the tree manually
By default, the Play() method on the PlayableGraph handles all the timing of the tree playback. However, for additional control, it is possible to explicitly set the local time of a Playable For instance, in this example, we pause the playback, and control the time using a variable.
using UnityEngine;
using UnityEngine.Experimental.Director;
[RequireComponent (typeof (Animator))]
public class PlayWithTimeControl : MonoBehaviour
{
public AnimationClip clip;
public float time;
PlayableGraph playableGraph;
PlayableHandle clipPlayableHandle;
void Start ()
{
clipPlayableHandle = AnimationPlayableUtilities.PlayClip(GetComponent(), clip, out playableGraph);
// stops time from progressing automatically.
clipPlayableHandle.playState = PlayState.Paused;
}
void Update ()
{
// Control the time manually
clipPlayableHandle.time = time;
}
void OnDisable()
{
// Destroys all Playables and Outputs created by the graph.
playableGraph.Destroy();
}
}
Controlling the play state of the tree
Similarly, the state of the tree, or a branch of the tree, can be set by changing the Playable.state parameter. The state will propagate to all children of the node, regardless of their previous state. (Keep in mind that if a child node was explicitly paused, setting a parent to Playing state will also set this child to Playing state).
Creating ScriptPlayable
The Playable API allows to create custom playables by deriving from ScriptPlayable. By overloading the PrepareFrame method, we can handle the nodes as desired. In this example, the goal is to have animation clips play one after the other. We change the weight of the nodes so that only one clip plays at a time, and we adjust the local time of the clips so that they start at the moment they get activated.
Similarly, Custom Playables can also implement the OnSetPlayState and OnSetTime methods, to implement custom behaviours when a playable’s state or local time has changed.
and an example of script playable:
using UnityEngine;
using UnityEngine.Experimental.Director;
public class PlayQueuePlayable : ScriptPlayable
{
private int m_CurrentClipIndex = -1;
private float m_TimeToNextClip;
private PlayableHandle mixer;
public void Initialize(AnimationClip[ ] clipsToPlay)
{
handle.inputCount = 1;
mixer = handle.graph.CreateAnimationMixerPlayable(1);
handle.graph.Connect(mixer,0, handle,0);
mixer.inputCount = clipsToPlay.Length;
for (int clipIndex = 0 ; clipIndex < mixer.inputCount ; ++clipIndex)
{
handle.graph.Connect(handle.graph.CreateAnimationClipPlayable(clipsToPlay[clipIndex]),0, mixer, clipIndex);
}
}
override public void PrepareFrame(FrameData info)
{
// Advance to next clip if necessary
m_TimeToNextClip -= (float)info.deltaTime;
if (m_TimeToNextClip <= 0.0f)
{
m_CurrentClipIndex++;
if (m_CurrentClipIndex < mixer.inputCount)
{
var currentClip = mixer.GetInput(m_CurrentClipIndex).GetObject();
// Reset the time so that the next clip starts at the correct position
currentClip.handle.time = 0;
m_TimeToNextClip = currentClip.clip.length;
}
}
// Adjust the weight of the inputs
for (int clipIndex = 0 ; clipIndex < mixer.inputCount ; ++clipIndex)
{
if (clipIndex == m_CurrentClipIndex)
mixer.SetInputWeight(clipIndex, 1.0f);
else
mixer.SetInputWeight(clipIndex, 0.0f);
}
}
}
[RequireComponent (typeof (Animator))]
public class PlayQueue : MonoBehaviour
{
public AnimationClip[ ] clipsToPlay;
PlayableGraph playableGraph;
void Start ()
{
playableGraph = PlayableGraph.CreateGraph();
var playQueue = playableGraph.CreateScriptPlayable().GetObject();
playQueue.Initialize(clipsToPlay);
var playableOutput = playableGraph.CreateAnimationOutput(“Animation”, GetComponent());
playableOutput.sourcePlayable = playQueue;
playableGraph.Play();
}
void OnDisable()
{
// Destroys all Playables and Outputs created by the graph.
playableGraph.Destroy();
}
}
@pierrepaul
I see you also got rid of the GC allocs when calling SetInputWeight()! Great job!
By the way, is there a planned way to apply AvatarMasks to certain Playables?
I tried to accomplish this by creating an empty AnimatorController with the mask I want on its base layer, and play some AnimClipPlayables on that AnimationController, but it didn’t seem to work
Hi ! we are working on it ! We will publish the AnimationLayerMixerPlayable which allows to specify a mask.
In 5.6b6, I see that AnimationLayerMixerPlayable was added, but I’m not sure how to add one to a PlayableGraph.
I’m basically looking for a PlayableGraph.CreateAnimationLayerMixerPlayable() but it’s not there. Is there another way right now to add an AnimationLayerMixerPlayable to a graph?
Hi. I’ve been trying to build a small animation system with new playables for the last 2 days, but it either behaves weirdly or crashes the editor entirely.
What I’m trying to achieve is to have a graph in which I insert animations as neccessary (for example, an object can contain an animation on how to use it and I don’t want to put it in every graph of every NPC, but rather to dynamically add it and crossfade from controller to it and back).
I’ve tried the animation mixer, and added a controller and a clip to it (or two controllers, or two clips, or a clip and two controllers, you name it).
Code: using UnityEngine;using System.Collections;using UnityEngine.Experimental.Di - Pastebin.com
Results
What happens is that when an NPC comes close enough to the sphere an interaction is enacted, which asks the component above to play a clip of “use” animation, which starts with the weight of 0. Unfortunately for some reason when I add it to the mixer it forces an object to jump back to its origin.
I’ve also tried this code, using the generic mixer and creating everything manually. Unfortunately it crashes if I connect a playable to it after I’ve asked it to start playing.
Any insights?
@KreolDev
I’d like to see a dev comment on this, but I’m not sure playables are meant to be used like that.
I have a situation that is somewhat similar to yours: a player can pickup skills, and each skill contains its AnimationClip. What I do is that instead of modifying the playables graph on the fly whenever a skill is picked up, I generate the entire graph with all the possible skill anims on start (there is some sort of manager somewhere that knows of every possible skill in the game). This way you don’t have to append stuff to the graph while it’s running and possibly cause weird behaviour.
However, I understand the appeal of using your approach, and this is what I originally wanted to do before I noticed that it caused problems. So if a dev knows of a way to do this, I’d like to know
Edit: maybe try Playablegraph.Stop(), then add the new playable, then Playablegraph.Play() and see if it fixes the problem? It might reset all the timings and/or weights so you’ll have to store the data of the current state somewhere and reapply it on Play()
@KreolDev :
If you haven’t already done so, please file a bug with the case where it crashes. It’s never normal for Unity to crash.
I think you’re supposed to use PlayableGraph.DestroyPlayable, not Destroy directly though (wild guess).
As for the jump, it’s hard to tell at a glance. I get the feeling that something you’re doing is forcing a reset of the graph. I’m not sure exactly what, but you can feel free to file a bug with it; we’ll look at it.
@PhilSA :
Your approach should be more efficient. Any time new clips are added, we need to recalculate the bindings (the total list of everything the Animator writes to), and this is costly. The cost scales on the number of different curves you write to, and the number of clips you have. This cost already exists in every Animation solution in Unity (Humanoid, Generic, Legacy), it’s not specific to Playables.
Also, If you connect clips that write to new curves mid-play, you’ll also save new default values, that might or might not be what you want.
By pre-connecting the clips, you won’t have to pay this cost every time you need to change clips, and you’ll make sure to get the default values from scene load/instantiation.
Thanks for the reply. I’ll definitely make some tests to try modifying a graph “at runtime” to see what kind of performance impact it creates, and how it affects resetting the graph and such.
While you’re here, I’d like to ask: is there another way to create a playable in a graph, aside from the PlayableGraph.CreateXPlayable() methods? I’m asking because I can see that AnimationLayerMixerPlayable has made it to the beta releases, but I see no way of creating them in a graph
For example, would this work?
_baseMixer = AnimationPlayableUtilities.PlayMixer(Animator, 1, out _playableGraph);
AnimationLayerMixerPlayable animLayerMixerPlayable = new AnimationLayerMixerPlayable();
_playableGraph.Connect(animLayerMixerPlayable, 0, _baseMixer, 0);
No, this is the only creation method.
AnimationLayerMixerPlayable still needs some other structures that are Editor only to be exposed in order to work properly in C#-land. I’m not sure why it got exposed in 5.6, maybe so the graph visualizer can show it, even if it’s not really interactable.
It will be fully exposed in 2017.1.
Hi David,
Any chance we will see an additive blend mixer with the 2017.1 release?
My hope is to replace mechanim as the root animation controller of my character and have some layers custom playables and others state machines. As some of my mechanim layers are additively blended I don’t believe this is possible without some sort of additive mixer.
The layer mixer playable should have additive options on it.
But additive still has to be done at the layer level, not at the individual mixer level.
That works for our purposes. Looking forward to it!
Will we be able to run simple animations without animator or animator controller in the future ?
There’s no plans for componentless animation in the near future. You’ll always need some kind of place to store the animation bindings (how the clips write to the properties).
Thanks for the answer. Now I know that I have to use Animator even if I need to play single animation clip.