Retrieve all Animator States and set them manually in code

Hafnernuss picture Hafnernuss · Jan 18, 2017 · Viewed 14.2k times · Source

I've stumbled upon something I never imagined to be a problem but... it is. I am writing a visualiazion/simulation toolkit using unity 5.5.

The used model is already imported and animation clips are set via the editor. This is what my animation state controller lookes like:

Animation State Controller I want to display the AnimationStates in a DropdownMenu. Whenever a user selects an entry, the current animation state should be set to the selected one.

e.G. User selected Autoplay: State set to clip Autoplay, after that to SimpleRotate, then to ComplexRotate and again to Autoplay (just a simple loop).

But if the user selects Idle, ComplexRotate 0 or SimpleRotate 0, the animation should play once and then stay at the end of the clip. (Just like I did it in the animation controller.

Heres a rather pseudo-version of what i want:

//Retrieve all states to populate the dropdown:
List<AnimationState> states = myAnimationController.GetAllStates();

foreach(AnimationState state in states)
  Dropdown.add(state.name)


//callback
OnDropdownIndexChanged(int item)
{
  string stateName = ..... //removed for simplicity
  myAnimationController.setState(stateName)
}

On top of that it would be nice if I could check the transitions FROM and TO the states. (Autoplay is the only state that should be shown in the dropdown for the loop)

Can this be realized with a custom Animation Controller? Or did I miss something?

Answer

Max Auer picture Max Auer · Jan 18, 2017

I see two options here. A rather simple one and a bit more complex, but robust, one.

Option One

You use the RuntimeAnimatorController. This will get you all the AnimationClips in the given Animator at runtime. You can play a State in an AnimatorController at any given time by using Animator.Play(StateNameOrHash). Unfortunately, for this option, this will only play a State by its Name or NameHash (as statet in the docs). Because we only have the AnimationClips and not the States from the Controller this option would involve to rename the States in the controller to the exact name of the Animation Clip. Then you could play a State like this:

myAnimator.Play(myAnimationClip.name);

This is a fast option to get you started but not a good option in the long run because it has essentially two drawbacks:

  • You could only use a flat hierarchy in your AnimatorController. No SubStateMachines/Blendtrees because they are not represented with an AnimationClip. Therefore you would not be able to retrief these States through the RuntimeAnimatorController because it only returns the AnimationClips
  • The AnimatorController could be hard to understand because the states are named like the AnimationClips. I dont know your situation here but it would be better to use descriptive names then the names from the AnimationClips for better understanding.

Option Two

This option is more complex. I will give you a quick overview of the Workflow and then explain the parts in more detail or give references to get you started!

  1. [Editor Script] Generate a script with all the States from your AnimatorController. (Probably the "hardest" part)
  2. Use the generated Script to fill your dropdown with the States
  3. Play your state :)

Part 1: Use an Editor Script to extract all the States from your AnimatorController. This link should give you an hint on how to achieve this. Short (not tested) example code:

private void writeAllStates()
{
    AnimatorControllerLayer[] allLayer = controller.layers;

    List<string> stateNames = new List<string>();

    for (int i = 0; i < allLayer.Length; i++)
    {
        ChildAnimatorState[] states = allLayer[i].stateMachine.states;

        for (int j = 0; j < states.Length; j++)
        {
            // you could do additional filtering here. like "would i like to add this state to my list because it has no exit transition" etc
            if (shouldBeInserted(states[i]))
            {
                // add state to list
                stateNames.Add(states[j].state.name);
            }
        }
    }

    // now generate a textfile to write all the states
    FileGenerator.createFileForController(controller, stateNames);
}

Keep in mind: the used AnimatorController is within the UnityEditor.Animations namespace. This can be a public field in an EditorScript to easily give you access.

Part 2: Your scriptfile should create a class like this one:

public class AnimatorControllerStates
{
    public List<string> states = new List<string>
    {
        "Idle",
        "ComplexRotate 0",
        "Autoplay",
        ...
    }
}

Name the class somehow related to your AnimatorController you created it for. And make this class a field in your runtime script. Then you could get access to all your states like this:

myAnimatorControllerStates.states

This array can fill your dropdown. You would save the index of the dropdown and then can play the state by the index.

Part 3: Now it would be realy easy to just play your state in your script:

string state = myAnimatorControllerStates.states[mySelectedIndex];
myAnimatorController.Play(mySelectedIndex);

I dont know the scope of your project but to have a maintainable application I would prefer option two at any time.