I’m working with Playables trying to create my own animation controller system, but running into some issues when I switched from AnimationMixerPlayable to AnimationLayerMixerPlayable. I have a basic kind of blend tree with idle, walk, run and it works fine when I use AnimationMixerPlayable, but when I switch to AnimationLayerMixerPlayable, the animations blend very strangely.
See video below where the first part shows the correct blending with AnimationMixerPlayable and the second part shows incorrect blending with AnimationLayerMixerPlayable. Literally, the only thing different is the type of playable being used:
Documentation for Playables is thin and tutorials on using it are hard to find so I’m kind of winging this trying to figure out how to make it all work. Does anyone know why the blending would work differently between these two playables?
Also, note that at times the walking/running looks correct, but that’s right at the point where the blending is 100% for walk or run and 0% for any other inputs.
I have no idea why it would be doing that, but if you are trying to replicate a blend tree you should probably be using a regular mixer, not a layer mixer. The primary differences are that layer mixers support masks and additive blending, which are not relevant to that situation.
No, I’m doing more than that. In addition to blending the idle, walk, and run, I also want to be able to inject an addiive animation such as waving while walking, attacking while running, etc. That’s the whole reason I switched to the layers because it was the only way to make it happen.
I’m really hoping someone has some clue as to what I could be doing wrong. You’re the Animancer author so I could certainly use some advice. Maybe it’s just something simple in my setup. I really am doing all this kind of blind because there are no docs on using the layer mixer or examples for most of the Playables system.
Just use multiple mixers. Animancer would create a graph something like this:
- Layer Mixer
- Regular Mixer for layer 0
- Regular Mixer for Idle/Walk/Run
- Animation Clip Playable for Idle
- Animation Clip Playable for Walk
- Animation Clip Playable for Run
- Playables for other clips and stuff
- Regular Mixer for layer 1
Hmmm. I thought about that, but then I thought that you can only set a single source playable for the playable output so how does it play both mixers at the same time? I tried multiple outputs, but couldn’t seem to get them to work together. This is one of those areas that just isn’t clearly explained in the docs.
The layer mixer is the only one connected to the output. The other mixers are connected to the layer mixer. And the animation playables are connected to those mixers.
It might be helpful to download Animancer Lite and the Playable Graph Visualiser so you can see what my graphs look like in the examples (Linear Blending in particular).
@Kybernetik I can’t thank you enough. Your advice sent me in the right direction and I have it fully working now. I think the light bulb is starting to go on with how this is supposed to be structured. Thank you so much.
@Kybernetik I thought I had this working perfectly, but the blending doesn’t work as expected. I struggled with this for a long time today thinking I must be doing something wrong, but the same issue is happening when I simply use the sample script MixAnimationSample.cs provided by Unity. Watch the video and you’ll see how the blending between walk and run gets all jerky.
Here is the script provided as an example by Unity, which yields the same results as seen in the video:
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Animations;
[RequireComponent(typeof(Animator))]
public class MixAnimationSample : MonoBehaviour
{
public AnimationClip clip0;
public AnimationClip clip1;
public float weight;
PlayableGraph playableGraph;
AnimationMixerPlayable mixerPlayable;
void Start()
{
// Creates the graph, the mixer and binds them to the Animator.
playableGraph = PlayableGraph.Create();
var playableOutput = AnimationPlayableOutput.Create(playableGraph, "Animation", GetComponent<Animator>());
mixerPlayable = AnimationMixerPlayable.Create(playableGraph, 2);
playableOutput.SetSourcePlayable(mixerPlayable);
// Creates AnimationClipPlayable and connects them to the mixer.
var clipPlayable0 = AnimationClipPlayable.Create(playableGraph, clip0);
var clipPlayable1 = AnimationClipPlayable.Create(playableGraph, clip1);
playableGraph.Connect(clipPlayable0, 0, mixerPlayable, 0);
playableGraph.Connect(clipPlayable1, 0, mixerPlayable, 1);
// Plays the Graph.
playableGraph.Play();
}
void Update()
{
weight = Mathf.Clamp01(weight);
mixerPlayable.SetInputWeight(0, 1.0f - weight);
mixerPlayable.SetInputWeight(1, weight);
}
void OnDisable()
{
// Destroys all Playables and Outputs created by the graph.
playableGraph.Destroy();
}
}
Interesting that you said that because I was thinking that was the issue right off the bat and I tried to sync them, but didn’t have any success, but I guess I gave up too quickly. I will look at it closer and try to get that working.
Mixing with a LayerMixer isn’t the same as a Mixer
Mixer is the sum of all the clips each one multiplied by a weight.
LayerMixer overrides previous data with a weight.
Starting from last port to first port on a layer mixer and assuming the sum of all weights will be <= 1, the weights for each input port would be calculated this way:
AnimationLayerMixerPlayable layerMixer; // Fill this somewhere
List<float> weightWantedForPort; // Fill this somewhere
float remainingWeight = 1;
for (int i = layer.Playable.GetInputCount() - 1; i >= 0; i--) {
float wantedWeight = weightWantedForPort[i];
layerMixer.SetInputWeight(i, wantedWeight / remainingWeight);
remainingWeight -= wantedWeight;
}
I have no idea how to handle weights larger than 1 or how would they work with this kind of mixer.
This is interesting because it allows you to set up a clip (mixer, or whatever) on the first input, with a weight of 1, and use it as the default so in case a property isn’t animated, it will fall back to the animated property of this clip instead of the unreliable default property stored on the animator.
To do this, set that default clip to the input port 0 and on the previous code iterate up to i >= 1 instead of i >= 0.