So I have an animator on my GameObject. I need to change the animation clip/motion of different states in my animator to the one I assign in my EditorGUILayout.ObjectField. Problem is, I have no idea how to access those clips. This is purely Editor Scripting.
2 Answers
This is purely Editor Scripting.
Yes, you can do it. But take note that manipulating Animator's state machine and clip inside ONLY possible in Editor. (For automation editing/scripting, etc) It is very complicate process but I will try to cover as much as I can.
For side note, AnimationClipOverrides in Dmitri's answer is for game runtime. It 'overrides' the original animation clip to another set of animation clips.
Obtain reference to Editor's Animator
First in editor script or EditorGUI, obtain reference to UnityEditor.Animations.AnimatorController you are going to wotk with by any method, for example via field in custom EditorWindow:
using UnityEditor;
using UnityEditor.Animations;
AnimatorController m_CurrentAnimator;
m_CurrentAnimator = (AnimatorController)EditorGUILayout.ObjectField( "Animator", m_CurrentAnimator, typeof( AnimatorController ), false );
Access layer and get all states inside
First is layer that you have to peel off, for simplicity, I will operate on layer 0. Then there will be AnimatorStateMachine inside it, I will use List to collect all states recursively. (also travel through nesting sub-statemachines)
AnimatorControllerLayer[] layers = m_CurrentAnimator.layers;
AnimatorControllerLayer workingLayer = layers[0];
List<AnimatorState> stateList = _ExpandStatesInLayer( sourceStateInfoLayer.stateMachine );
where
List<AnimatorState> _ExpandStatesInLayer( AnimatorStateMachine sm, List<AnimatorState> collector = null )
{
if( collector == null )
collector = new List<AnimatorState>( );
foreach( var state in sm.states )
{
collector.Add( state.state );
foreach( var subSm in sm.stateMachines ) // Jump into nested state machine
_ExpandStatesInLayer( subSm.stateMachine, collector );
}
return collector;
}
Peek into state's content (AnimationClip/BlendTree)
After long fight, now we can see contents in each state. The code is self explained. It is up to you how to modify these state.
foreach( var state in stateList )
{
Motion m = state.motion;
if( m is AnimationClip ) // It is single animation clip
Debug.Log( "clip " + m.name );
if( m is BlendTree ) // It is blendtree with multiple clips!
Debug.Log( "tree " + m.name );
state.motion = null; // TODO: Set something else to it.
}
- Dont forget a call to UnityEditor.EditorUtility.SetDirty if you actually modify it. Or better alternative UnityEditor.Undo.RecordObject. (It also set dirty state)
- If layer is SYNC layer (copy states from source layer), the workflow will be a bit different when getting state list and getting motion attached to it. It is not covered in this answer. You can lookup potential API from related class documents.
You cannot access the clip in the animator directly. Here is an example of how I used clip replacement. For more information, look into Unity's documents. https://docs.unity3d.com/ScriptReference/AnimatorOverrideController.ApplyOverrides.html
using UnityEngine;
using System.Collections.Generic;
public class AnimationClipOverrides : List<KeyValuePair<AnimationClip, AnimationClip>>
{
public AnimationClipOverrides(int capacity) : base(capacity) { }
public AnimationClip this[string name]
{
get { return this.Find(x => x.Key.name.Equals(name)).Value; }
set
{
int index = this.FindIndex(x => x.Key.name.Equals(name));
if (index != -1)
this[index] = new KeyValuePair<AnimationClip, AnimationClip>(this[index].Key, value);
}
}
}
public class PlayerScript : MonoBehaviour
{
[SerializeField]
private AnimationClip[] m_Animations;
private Animator anim;
private AnimatorOverrideController animatorOverrideController;
private AnimationClipOverrides clipOverrides;
void Start()
{
anim = GetComponent<Animator>();
animatorOverrideController = new AnimatorOverrideController(anim.runtimeAnimatorController);
anim.runtimeAnimatorController = animatorOverrideController;
clipOverrides = new AnimationClipOverrides(animatorOverrideController.overridesCount);
animatorOverrideController.GetOverrides(clipOverrides);
}
public void Play(int numberClip)
{
clipOverrides["myAnimation"/*name *.anim file in State inspector*/] = m_Animations[numberClip];
animatorOverrideController.ApplyOverrides(clipOverrides);
anim.Play("start");
}
}