I was just getting started with mecanim today. I really like the way it’s set up, but a few questions arose:
-
how can I just trigger an animation (e.g. “Dying”) and fade it in immediately?
-
this unity doc page states that you should “Implement a small AI layer to control the Animato. You can make it provide simple callbacks for OnStateChange, OnTransitionBegin etc…”. I think events are very much needed for fine-grained animation control, but I have no idea how to implement that given my third question:
-
how to get a list of states/transitions (and their names/hashes) from an Animator?
This is a method that I made for my game, and it works:
void Start () {
_animator = gameObject.GetComponent<Animator>();
}
public AnimationClip GetAnimationClip(string name) {
if (!_animator) return null; // no animator
foreach (AnimationClip clip in _animator.runtimeAnimatorController.animationClips) {
if (clip.name == name) {
return clip;
}
}
return null; // no clip by that name
}
This is now available inside unity. It returns all the animation clips used by an actor. It works on both legacy prefabs and mecanim prefabs.
AnimationUtility.GetAnimationClips(GameObject)
Here’s the editor-time method I use to get an array of states from the Animator component:
private static AnimatorState[] GetStateNames(Animator animator) {
AnimatorController controller = animator ? animator.runtimeAnimatorController as AnimatorController : null;
return controller == null ? null : controller.layers.SelectMany(l => l.stateMachine.states).Select(s => s.state).ToArray();
}
Very good content here. My problem is I’m setting the layer weight and it’s playing at the wrong starting point of the animation. I want to be able to reset the state time so the animation plays from the beginning. Cross-fading just masks the issue.
I was able to at least determine the length of the state animation given the animation clip is found within Resources.
if (Random.Range(0, 2) == 0)
{
SetLayerWeight("Attack", 1f);
SetLayerWeight("Cast", 0f);
}
else
{
SetLayerWeight("Attack", 0f);
SetLayerWeight("Cast", 1f);
}
SetLayerWeight("Idle", 0f);
UnityEditorInternal.AnimatorController ac = m_animator.runtimeAnimatorController as UnityEditorInternal.AnimatorController;
for (int count = 0; count < m_animator.layerCount; ++count)
{
if (m_animator.GetLayerWeight(count) == 1f)
{
Debug.Log(m_animator.GetLayerName(count));
Debug.Log(ac.GetLayer(count).stateMachine.GetState(0).name);
m_animator.Play(ac.GetLayer(count).stateMachine.GetState(0).name);
AnimationClip clip = (AnimationClip)Resources.Load(ac.GetLayer(count).stateMachine.GetState(0).name, typeof(AnimationClip));
if (clip)
{
Debug.Log(clip.length);
m_timer2 = DateTime.Now + TimeSpan.FromSeconds(clip.length);
break;
}
}
}
So we used a similar solution, but we did it because we have our own LOD management instead of Unity’s LOD.
Ours is called TDLLOD. We found that in runtime (not in editor) that setting the state on
an Animator causes it to permantly lock up. So we first check that it is enabled and accessible.
This code works for us. Note: watch out for the difference between name and uniqueName!
public bool IsValidAnimatorState( Animator a, string stateName, int layer )
{
bool ret = false;
try
{
layer = CheckLayer(layer);
if(layer == -1)
{
layer = 0;
}
UnityEditorInternal.AnimatorController ac = a.runtimeAnimatorController as UnityEditorInternal.AnimatorController;
if( !ac )
{
return false;
}
//int layerCount = ac.layerCount;
//Debug.Log(string.Format("Layer Count: {0}", layerCount));
// Names of each layer:
// for (int ly = 0; ly < layerCount; ly++)
// {
// Debug.Log(string.Format("Layer {0}: {1}", layer, ac.GetLayer(ly).name));
// }
UnityEditorInternal.AnimatorControllerLayer ly = ac.GetLayer(layer);
if( ly == null )
{
return false;
}
UnityEditorInternal.StateMachine sm = ly.stateMachine;
if( !sm )
{
return false;
}
for(int i=0; i< sm.stateCount; i++)
{
// .name is like "Buck_death" uniqueName is like "Base Layer.Buck_death".
// Debug.Log(string.Format("State: {0}", sm.GetState(i).name));
ret |= (sm.GetState(i).name == stateName);
}
if(ret == false)
{
//stateValidDebugFlag = true;
}
}
catch( System.Exception exx )
{
}
return ret;
}
My answer is very simple and cool
int hash = Animator.StringToHash("Dying");
GetComponent().Play(hash, 0, 1);
I am getting the name of all Animation States in my Animator component in my Reset Method. I do pass through mecanim states to achieve that. Take a look at my code it might help you list all mech anim states.[128495-nameallanimstates.txt|128495]
I made an update working with the current APIs. I hope someone finds this helpful!
using UnityEditor.Animations;
public void PrintStates()
{
// Get a reference to the Animator Controller:
AnimatorController ac = _anim.runtimeAnimatorController as AnimatorController;
// Number of layers:
int layerCount = ac.layers.Length;
Debug.Log(string.Format("Layer Count: {0}", layerCount));
// Names of each layer:
for (int layer = 0; layer < layerCount; layer++)
{
Debug.Log(string.Format("Layer {0}: {1}", layer, ac.layers[layer].name));
}
// States on layer 0:
AnimatorStateMachine sm = ac.layers[0].stateMachine;
ChildAnimatorState[] states = sm.states; // Also: sm.states
foreach (ChildAnimatorState s in states)
{
Debug.Log(string.Format("State: {0}", s.state.name));
}
}