Ben Traje
← Back to unity

Automating Unity Animator Controllers with C# Editor Scripts

19 Sep 26 (1mo ago)

Streamline Your Animation Pipeline

Setting up complex Animator Controllers in Unity manually can be a tedious, error-prone process—especially when standardizing character setups across a large project. By leveraging Unity's UnityEditor.Animations API, technical artists and developers can fully automate the creation and modification of state machines, parameters, and transitions using C# Editor scripts.

Here is how you can programmatically generate and update your Animator Controllers to save time and enforce project consistency.

1. Creating a New Animator Controller Programmatically

To generate a controller from scratch, you need to define its save path, build the required parameters, and configure the states and transitions.

The following script creates a controller, adds "Idle" and "Walk" states, sets up a "Speed" float parameter, and explicitly defines the transition blending rules.

using UnityEngine;
using UnityEditor;
using UnityEditor.Animations;

public class AnimatorGenerator
{
    [MenuItem("Tools/Animation/Generate Setup Animator")]
    public static void CreateAnimator()
    {
        string path = "Assets/AutoGeneratedController.controller";
        
        // 1. Create the controller asset
        AnimatorController controller = AnimatorController.CreateAnimatorControllerAtPath(path);

        // 2. Add parameters
        controller.AddParameter("Speed", AnimatorControllerParameterType.Float);

        // 3. Access the root state machine (Base Layer)
        AnimatorStateMachine rootStateMachine = controller.layers[0].stateMachine;

        // 4. Create States
        AnimatorState idleState = rootStateMachine.AddState("Idle");
        AnimatorState walkState = rootStateMachine.AddState("Walk");
        
        // (Optional) Assign an AnimationClip to the state:
        // idleState.motion = AssetDatabase.LoadAssetAtPath<AnimationClip>("Assets/Animations/Idle.anim");

        // 5. Create Transition: Idle -> Walk
        AnimatorStateTransition idleToWalk = idleState.AddTransition(walkState);
        idleToWalk.hasExitTime = false;
        idleToWalk.hasFixedDuration = true;
        idleToWalk.duration = 0.1f;
        idleToWalk.offset = 0f;
        idleToWalk.interruptionSource = TransitionInterruptionSource.None;
        idleToWalk.AddCondition(AnimatorConditionMode.Greater, 0.1f, "Speed");

        // 6. Create Transition: Walk -> Idle
        AnimatorStateTransition walkToIdle = walkState.AddTransition(idleState);
        walkToIdle.hasExitTime = false;
        walkToIdle.hasFixedDuration = true;
        walkToIdle.duration = 0.15f;
        walkToIdle.AddCondition(AnimatorConditionMode.Less, 0.1f, "Speed");

        // 7. Save assets to disk
        AssetDatabase.SaveAssets();

        Debug.Log($"Successfully generated Animator Controller at: {path}");
    }
}

2. Modifying an Existing Animator Controller

Often, you will need to update an existing Animator rather than overwriting it. The critical challenge here is preventing duplicate states and parameters when a script runs multiple times.

By using AssetDatabase.LoadAssetAtPath to target the existing controller and utilizing LINQ (System.Linq) to verify if elements already exist, you can safely append or modify data.

using UnityEngine;
using UnityEditor;
using UnityEditor.Animations;
using System.Linq; 

public class AnimatorModifier
{
    [MenuItem("Tools/Animation/Modify Existing Animator")]
    public static void ModifyAnimator()
    {
        string path = "Assets/AutoGeneratedController.controller";
        
        // 1. Load the existing controller
        AnimatorController controller = AssetDatabase.LoadAssetAtPath<AnimatorController>(path);

        if (controller == null)
        {
            Debug.LogError($"No Animator Controller found at {path}.");
            return;
        }

        // 2. Add a parameter safely (avoid duplicates)
        string paramName = "IsRunning";
        if (!controller.parameters.Any(p => p.name == paramName))
        {
            controller.AddParameter(paramName, AnimatorControllerParameterType.Bool);
        }

        AnimatorStateMachine rootStateMachine = controller.layers[0].stateMachine;

        // 3. Add or update a state safely
        string stateName = "Run";
        AnimatorState runState;
        
        var existingState = rootStateMachine.states.FirstOrDefault(s => s.state.name == stateName);
        
        if (existingState.state != null)
        {
            // Modify existing state
            runState = existingState.state; 
            runState.speed = 1.5f; 
        }
        else
        {
            // Create new state
            runState = rootStateMachine.AddState(stateName); 
        }

        // 4. Mark dirty and save
        EditorUtility.SetDirty(controller);
        AssetDatabase.SaveAssets();

        Debug.Log("Animator modification complete.");
    }
}

Crucial Workflow Details & Best Practices

To ensure your Editor scripts function flawlessly within a production environment, keep the following technical details in mind:

  • Assigning Animation Clips: Creating a state simply builds the node. To make the state functional, load an AnimationClip asset and assign it to the state's motion property: myState.motion = myAnimationClip;.
  • EditorUtility.SetDirty(): Unity's serialization system does not always automatically detect changes made to memory-loaded assets via scripts. Always call EditorUtility.SetDirty(controller) before AssetDatabase.SaveAssets() when modifying existing objects to guarantee changes are written to the .controller file.
  • Blend Trees: You can also programmatically generate Blend Trees by instantiating a new BlendTree object, configuring its parameters (blendType, blendParameter), adding it to an AnimatorState's motion property, and using blendTree.AddChild() to populate it with clips.
  • Dynamic Targeting: Instead of hardcoding the string path, consider using Selection.activeObject to dynamically run the script on whichever Animator Controller you currently have selected in the Unity Project window.