Retrieve all Animator States and set them manually in code
Asked Answered
K

1

5

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?

Kristiekristien answered 18/1, 2017 at 0:15 Comment(3)
The only workaround I've came up with so far is: Do everything as seperate clip. The "loop" is one big clip from start to end, and the Simple/Complex rotation animations are seperat clips. That way I can simulate the behaviour. But I dont think thats the most elegant way to do this...Kristiekristien
For retreiving state info I would refer to the unity mecanim tutorial youtu.be/Xx21y9eJq1U?t=26m8s at the given time.Woodall
Thanks for the link. But the precedure always involves something like animator.IsInState("SomeStateName")... I want to retrieve the name of the states in my code. This approach just doesnt seem very intuitive to me as it is not very extensibleKristiekristien
A
9

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.

Antihero answered 18/1, 2017 at 20:13 Comment(4)
DANKE! I will try this on friday. The scope of the project is... (as always) scalable and therefore option two seems optimal. One small questions... Is there any way to group animations like in my image on the right side to one "super-animation"? Im quite new to the animation system of unity and therefore not quite sure what the right approaches are for most of the things.Kristiekristien
Thanks! ill try it in the evening or tomorrow, if everything works out, ill accept your answer as the correct oneKristiekristien
Hi, a quick test showed that approach A works fine, though its not very flexible. Ill definitly look into the second approach. :)Kristiekristien
I would add that it could be useful to add a build phase where this is done automatically on the build. I can see how people could forget to execute the editor script. Also, it might be wise to use a scriptable object for this!Frippery

© 2022 - 2024 — McMap. All rights reserved.