Search Unity

  1. We are migrating the Unity Forums to Unity Discussions. On July 12, the Unity Forums will become read-only. On July 15, Unity Discussions will become read-only until July 18, when the new design and the migrated forum contents will go live. Read our full announcement for more information and let us know if you have any questions.

Showcase Leveraging the AnimatorOverrideController in Timeline

Discussion in 'Timeline' started by olejuer, Jun 7, 2023.

  1. olejuer

    olejuer

    Joined:
    Dec 1, 2014
    Posts:
    211
    Hi,

    I am using Timeline a lot and I reuse the same timeline asset for many of my prefabs. I am using AnimatorTracks and it has bugged me that I cannot leverage the power of AnimatorOverrideControllers to have the same TimelineAsset play different animation clips depending on the Animator that is bound to the AnimationTrack.
    I have encountered several threads with similar problems, and with the information I gathered I have build a small utility that does the job and I think it's really helpful. So here it goes. You just add this component next to the playable director and everytime it plays, it will replace clips with their overrides, rebuild the graph, and restore the timeline asset.
    Use as you please :)

    Code (CSharp):
    1. /// <summary>
    2. /// Replace specific <see cref="AnimationClip"/>s in the <see cref="TimelineAsset"/>s <see cref="AnimationTrack"/>s.
    3. /// This can be used to repurpose a timeline for different <see cref="PlayableDirector"/>s, leveraging the
    4. /// <see cref="AnimatorOverrideController"/>s bound to the animation tracks.
    5. /// This works non-intrusively by restoring the timeline after building the playable graph.
    6. /// </summary>
    7. [RequireComponent(typeof(PlayableDirector))]
    8. public class ApplyAnimatorOverrideControllerToTimeline : MonoBehaviour
    9. {
    10.     private PlayableDirector _playableDirector;
    11.  
    12.     private void Awake()
    13.     {
    14.         _playableDirector = GetComponent<PlayableDirector>();
    15.     }
    16.  
    17.     private void OnEnable()
    18.     {
    19.         _playableDirector.played += OnPlayed;
    20.     }
    21.  
    22.     private void OnDisable()
    23.     {
    24.         _playableDirector.played -= OnPlayed;
    25.     }
    26.  
    27.     /// <summary>
    28.     /// When the timeline is played, replace the clips and rebuild the playable graph.
    29.     /// Then reset the original values. This way the same timeline asset can be used by multiple directors, each
    30.     /// applying its own overrides according to the respectively bound animator controllers.
    31.     /// </summary>
    32.     private void OnPlayed(PlayableDirector playableDirector)
    33.     {
    34.         ChangePackagedAnimationClips(true);
    35.         playableDirector.RebuildGraph();
    36.         ChangePackagedAnimationClips(false);
    37.     }
    38.  
    39.     /// <summary>
    40.     /// Replace the <see cref="AnimationClip"/>s in the <see cref="TimelineAsset"/> according to the bound
    41.     /// <see cref="AnimatorOverrideController"/>s.
    42.     /// </summary>
    43.     private void ChangePackagedAnimationClips(bool useOverrides)
    44.     {
    45.         var timeline = _playableDirector.playableAsset as TimelineAsset;
    46.         if (timeline == null) return;
    47.  
    48.         foreach (AnimationTrack animationTrack in timeline.GetOutputTracks().OfType<AnimationTrack>())
    49.         {
    50.             var animator = _playableDirector.GetGenericBinding(animationTrack) as Animator;
    51.             if (animator == null) continue;
    52.             var overrideController = animator.runtimeAnimatorController as AnimatorOverrideController;
    53.             if (overrideController == null) continue;
    54.             var overrides = new List<KeyValuePair<AnimationClip, AnimationClip>>(overrideController.overridesCount);
    55.             overrideController.GetOverrides(overrides);
    56.  
    57.             foreach (TimelineClip timelineClip in animationTrack.GetClips())
    58.             {
    59.                 if (timelineClip.asset is AnimationPlayableAsset animationPlayableAsset)
    60.                 {
    61.                     (AnimationClip originalClip, AnimationClip overrideClip) = overrides
    62.                         .FirstOrDefault(keyValuePair => keyValuePair.Key == animationPlayableAsset.clip
    63.                                                         || keyValuePair.Value == animationPlayableAsset.clip);
    64.                     if (overrideClip == null) continue;
    65.                     animationPlayableAsset.clip = useOverrides ? overrideClip : originalClip;
    66.                 }
    67.             }
    68.         }
    69.     }
    70. }
     
    Yuchen_Chang and JeffG like this.