Search Unity

Question Reusing Prefabs with imported Animators

Discussion in 'Animation' started by g4borg, Dec 9, 2022.

  1. g4borg

    g4borg

    Joined:
    Mar 4, 2015
    Posts:
    3
    Hello!

    I am rather new in the Animation Topic, so I hope someone can nudge me in the right direction.

    I have bought an Asset, with lots of prefabs, that also contain LOD-Prefabs (nesting them), and all of the individual prefabs have already pre-defined Animators attached, including an AnimatorController for each model.

    All of them have however the same states defined, but with different animation clips, no transitions. The individual prefabs have rigs and meshes as child objects, the lod prefabs have the individual prefabs as child objects. This seems standard in most asset collections.

    Since I want to set up Animations with my own Animator on a root gameobject, where I create my state machine, with transitions, that I want to control, I don't want to repeat this by modifying the animators of each model.

    I saw the best practice is to attach AnimatorControlOverrides and re-define the motions there, but I did not want to do that by hand for such a large amount of prefabs.

    So my first thought was, I should try to create AnimatorControlOverrides out of the existing Animators of the prefabs, and use this on my root gameobject animator, so I could hot-swap the prefabs as a visual, and basicly copy the clips out of their native animator into my override, and deactivate it.

    I actually worked out, how to create the runtime control override, by reading the clips from the child animator, but now I have the issue, that the Animator on the prefab was the only animator that actually had control;

    My only manual coding solution is to create yet another MonoBehaviour, that I attach to all child prefabs at runtime, which copies and stores the animationclips, and then replace my own animationcontroller in the prefab animator, but create an override with the original imported clips, a solution I will try to achieve in the meantime.

    This however really brought me to the point, where I question my workflow, and maybe I overlook way easier methods to get this done properly. Any hints or help is appreciated.
     
  2. g4borg

    g4borg

    Joined:
    Mar 4, 2015
    Posts:
    3
    I ended up going for the backup -> create override solution, and it seems to have worked for now.

    Basically I created a Behaviour that will store my clips:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class AnimatorClipBackup : MonoBehaviour
    6. {
    7.     [SerializeField]
    8.     protected AnimationClip[] clips;
    9.  
    10.     protected void Start()
    11.     {
    12.         Animator animator = GetComponent<Animator>();
    13.         if (animator)
    14.             BackupLocalAnimationClips(animator);
    15.     }
    16.  
    17.     public void BackupLocalAnimationClips(RuntimeAnimatorController controller)
    18.     {
    19.         clips = new AnimationClip[controller.animationClips.Length];
    20.         controller.animationClips.CopyTo(clips, 0);
    21.     }
    22.  
    23.     public void BackupLocalAnimationClips(Animator animator)
    24.     {
    25.         BackupLocalAnimationClips(animator.runtimeAnimatorController);
    26.     }
    27.  
    28.     public void AssignClips(ref AnimationClipOverrides overrides)
    29.     {
    30.         // assigns clips from local copy to overrides, if they are present.
    31.         if (overrides == null)
    32.             return;
    33.         foreach (var clip in clips)
    34.         {
    35.             int index = overrides.FindIndex(x => x.Key.name.Equals(clip.name));
    36.             if (index >= 0)
    37.             {
    38.                 overrides[clip.name] = clip;
    39.             }
    40.         }
    41.     }
    42. }
    And within the root gameobject I basically used a function like this to apply this storage in runtime, and create the correct overrides.

    AnimationClipOverrides is from the unity documentation for AnimatorOverrides.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using Unity.VisualScripting;
    4. using UnityEngine;
    5.  
    6. public class ConvertChildAnimators : MonoBehaviour
    7. {
    8.     public Animator rootAnimator;
    9.  
    10.     protected void Start()
    11.     {
    12.         if (rootAnimator == null)
    13.             rootAnimator = GetComponent<Animator>();
    14.         OverrideChildAnimators();
    15.     }
    16.  
    17.     void OverrideChildAnimators()
    18.     {
    19.         if (rootAnimator == null)
    20.             return;
    21.         var animators = GetComponentsInChildren<Animator>();
    22.         foreach (var animator in animators)
    23.         {
    24.             if (rootAnimator != null && animator == rootAnimator)
    25.                 continue;
    26.             var clipBackup = animator.gameObject.GetComponent<AnimatorClipBackup>();
    27.             if (clipBackup == null)
    28.                 clipBackup = animator.gameObject.AddComponent<AnimatorClipBackup>();
    29.             clipBackup.BackupLocalAnimationClips(animator);
    30.  
    31.             var animatorControllerOverride = new AnimatorOverrideController(rootAnimator.runtimeAnimatorController);
    32.             AnimationClipOverrides overrideClips = new(animatorControllerOverride.overridesCount);
    33.             animatorControllerOverride.GetOverrides(overrideClips);
    34.             clipBackup.AssignClips(ref overrideClips);
    35.             animatorControllerOverride.ApplyOverrides(overrideClips);
    36.             animator.runtimeAnimatorController = animatorControllerOverride;
    37.         }
    38.     }
    39. }
    Now I can plan my state machine entirely in the rootAnimator (which is a disabled animator on my unit container) and replace the used prefab at runtime, where I run the override again.

    I am not sure if this is the correct and best way for my usecase, so if anyone has better ideas, feel free to post, until then I will continue with this.