Greetings,
so I wrote this simple animator with the Playable/graph API (simple, no crossfade etc, intended for 2D). Every time I transition to an animation it seem to write the default values for unused keys in that animation.
The player is made up of different body sprites. Head, legs, chest. etc. The example we’re having is a charge attack. Once the player enters the charge state, he plays the charge animation on loop, it’s supposed to only affect his feet sprite/position (that’s the only keys in the charge animation), but it also resets all his other sprites making them face the wrong direction.
I’ve tested this exact scenario in just pure Animator/mecanim and unchecking “Write Defaults” and I can confirm it does the correct behavior. It’s just I can’t seem to find a way to do this with Playables.
Here’s how I set the graph:
public void Initialize()
{
if (!Playback) Playback = gameObject.GetComponent<Animator>();
if (!Playback) Playback = gameObject.AddComponent<Animator>();
Playback.runtimeAnimatorController = null;
Playback.cullingMode = AnimatorCullingMode.AlwaysAnimate;
Playback.updateMode = AnimatorUpdateMode.Normal;
Playback.enabled = false;
if (States != null &&
States.Count > 0)
{
// Create the graph, mixer and ouput with connections
#if (UNITY_EDITOR)
Graph = PlayableGraph.Create("XAnimator-"+name);
#else
Graph = PlayableGraph.Create();
#endif
Graph.SetTimeUpdateMode(DirectorUpdateMode.Manual);
MixerNode = AnimationMixerPlayable.Create(Graph, States.Count);
var AnimationOutput = AnimationPlayableOutput.Create(Graph, "Animation", Playback);
AnimationOutput.SetSourcePlayable(MixerNode);
// Build lookup, create animation clip nodes and connect to graph
StateLookup.Clear();
for (int i = States.Count-1; i>=0; i--)
{
var It = States[i];
if (It.Clip == null)
{
error("State clip is null at index: " + i);
States.RemoveAt(i);
continue;
}
if (It.Clip.legacy)
{
error("State clip is legacy at index: " + i);
States.RemoveAt(i);
continue;
}
if (string.IsNullOrEmpty(It.Name))
{
error("State name is null/empty at index: " + i);
States.RemoveAt(i);
continue;
}
int StateHash = GetNameHash(It.Name);
if (StateLookup.ContainsKey(StateHash))
{
error("Duplicate state hash: " + It.Name);
States.RemoveAt(i);
continue;
}
StateLookup[StateHash] = It;
It.Node = CreateClipNode(It.Clip, i);
It.Index = i;
It.IsValid = true;
It.Hash = StateHash;
}
#if (UNITY_EDITOR)
// NOTE(Ali): Register graph so we can see it in this tool under Window/Analysis
GraphVisualizerClient.Show(Graph);
#endif
}
}
Here’s how an animation is played:
void PlayInternal(XAnimatorState State, float AtSpeed, bool ClearQueue=true)
{
if (CurrentState.IsValid)
{
MixerNode.SetInputWeight(CurrentState.Index, 0);
CurrentState.Node.Pause();
}
Playback.enabled = true;
if (!State.KeepAnimatedValues)
{
Playback.WriteDefaultValues();
}
MixerNode.SetInputWeight(State.Index, 1);
State.Node.SetTime(0);
State.Node.Play();
State.Speed = AtSpeed;
CurrentState = State;
if (ClearQueue) StateQueue.Clear();
}
The graph is ticked manually in an Update, basically:
public void Tick(float dt)
{
if (Graph.IsValid())
{
Graph.Evaluate(dt*FinalSpeed);
}
}
This is my state class for reference. Didn’t want to post everything, less noise:
[Serializable]
public class XAnimatorState
{
public string Name;
public AnimationClip Clip;
public bool KeepAnimatedValues;
[NonSerialized] public int Hash;
[NonSerialized] public int Index;
[NonSerialized] public AnimationClipPlayable Node;
[NonSerialized] public bool IsValid;
[NonSerialized] public float Speed = 1;
public float ClipLength => Clip? Clip.length:0;
public bool IsLoop => Clip? Clip.isLooping:false;
}
Note I tried all combinations of not doing ‘WriteDefaultValues’ at all, not disabling/re-enabling the Animator, etc. None seemed to have any affect.
I suspect it’s the way I’m playing the animations, maybe the output node needs to communicate to Animator to not reset values? I couldn’t find any methods about writing default values except for Animator.WriteDefaultValues.
In the attached gifs. BUG_CORRECT shows the correct expected behavior using Animator with WriteDefaults OFF in that second animation. Notice how he keeps his head the same sprite it was after the attack.
In BUG_WRONG.gif however, he attacks, plays the ‘charge’ animation which resets him facing south overriding the previous facing he had (north) after the attack.
Here’s what my graph looks like:
If there’s a piece of code you’d like to see that’s missing for information let me know!
Any help is appreciated, thanks!




