using System.Collections.Generic; using UnityEngine.Animations; using UnityEngine.Playables; namespace UnityEngine.Timeline { // Does a post processing of the weights on an animation track to properly normalize // the mixer weights so that blending does not bring default poses and subtracks, layers and // layer graphs blend correctly class AnimationOutputWeightProcessor : ITimelineEvaluateCallback { struct WeightInfo { public Playable mixer; public Playable parentMixer; public int port; } AnimationPlayableOutput m_Output; AnimationMotionXToDeltaPlayable m_MotionXPlayable; readonly List m_Mixers = new List(); public AnimationOutputWeightProcessor(AnimationPlayableOutput output) { m_Output = output; output.SetWeight(0); FindMixers(); } void FindMixers() { var playable = m_Output.GetSourcePlayable(); var outputPort = m_Output.GetSourceOutputPort(); m_Mixers.Clear(); // only write the final output in playmode. it should always be 1 in editor because we blend to the defaults FindMixers(playable, outputPort, playable.GetInput(outputPort)); } // Recursively accumulates mixers. void FindMixers(Playable parent, int port, Playable node) { if (!node.IsValid()) return; var type = node.GetPlayableType(); if (type == typeof(AnimationMixerPlayable) || type == typeof(AnimationLayerMixerPlayable)) { // use post fix traversal so children come before parents int subCount = node.GetInputCount(); for (int j = 0; j < subCount; j++) { FindMixers(node, j, node.GetInput(j)); } // if we encounter a layer mixer, we assume there is nesting occuring // and we modulate the weight instead of overwriting it. var weightInfo = new WeightInfo { parentMixer = parent, mixer = node, port = port, }; m_Mixers.Add(weightInfo); } else { var count = node.GetInputCount(); for (var i = 0; i < count; i++) { FindMixers(parent, port, node.GetInput(i)); } } } public void Evaluate() { float weight = 1; m_Output.SetWeight(1); for (int i = 0; i < m_Mixers.Count; i++) { var mixInfo = m_Mixers[i]; weight = WeightUtility.NormalizeMixer(mixInfo.mixer); mixInfo.parentMixer.SetInputWeight(mixInfo.port, weight); } // only write the final weight in player/playmode. In editor, we are blending to the appropriate defaults // the last mixer in the list is the final blend, since the list is composed post-order. if (Application.isPlaying) m_Output.SetWeight(weight); } } }