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
AnimationClipasset 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)beforeAssetDatabase.SaveAssets()when modifying existing objects to guarantee changes are written to the.controllerfile. - Blend Trees: You can also programmatically generate Blend Trees by instantiating a new
BlendTreeobject, configuring its parameters (blendType,blendParameter), adding it to anAnimatorState's motion property, and usingblendTree.AddChild()to populate it with clips. - Dynamic Targeting: Instead of hardcoding the string
path, consider usingSelection.activeObjectto dynamically run the script on whichever Animator Controller you currently have selected in the Unity Project window.