- Home /
Accessing a Mecanim state through scripting, then changing the associated clip--Can it be done?
I have a character using Mecanim with all its main animations run by a controller script, but sometimes I want it to run unique animations while interacting with specific objects and don't really want those stored in the animation controller. It would allow me to keep the state machine manageable while also allowing me to add objects more easily in the future.
I'm nearly certain that this can't be done with Mecanim in its current form, but I'm hoping I'm wrong. Below is an example of what I'd like to do. This would run in a method in the object after the player Gameobject is passed in as user:
Animator userAnim = user.GetComponent<Animator>();
userAnim.GetState("Interaction").SetClip(this.action); //I know this doesn't exist
userAnim.Play("Interaction", 0);
If the legacy system is the only way to do this, is it possible to run legacy animations on a character that is also using Mecanim or do I need to convert everything to legacy to pull this off?
Answer by smoggach · Oct 17, 2014 at 02:20 PM
There IS a way it's just a bit more tedious than you'd expect. http://docs.unity3d.com/ScriptReference/AnimatorOverrideController.html
It's too bad there isn't any real documentation on this part of $$anonymous$$ecanim. All I could find was this example from the forums from one of the devs from 2 years ago:
RuntimeAnimatorController myController = animator.runtimeAnimatorController;
AnimatorOverrideController myOverrideController = new AnimatorOverrideController();
myOverrideController.runtimeAnimatorController = myController;
myOverrideController["run"] = myRunFasterClip;
// Put this line at the end because when you assign a controller on an Animator, unity rebind all the animated properties
animator.runtimeAnimatorController = myOverrideController;
I changed my code to this: when the character initiates an interaction with the object,
Animator userAnim = user.GetComponent<Animator>();
AnimatorOverrideController myOverrideController = new AnimatorOverrideController();
myOverrideController.runtimeAnimatorController = userAnim.runtimeAnimatorController;
myOverrideController["Interaction"] = this.action;
userAnim.runtimeAnimatorController = myOverrideController;
This causes no errors, but both of the lines below cause the original animation for "Interaction" to play:
userAnim.SetBool("Interacting", true); //triggers a transition from the default state
userAnim.Play("Interaction", 0);
am I doing something wrong?
Yes. The first line sets the boolean to true which causes mecanim to change it's state. This triggers your animation to play.
The second line triggers it to play again.
Sorry, I wasn't saying I was using both lines together, just that either one was playing the old clip. I ran through the debugger a couple of times and realized that my problem was that I was using the state name ins$$anonymous$$d of the name of the actual clip I was trying to override:
myOverrideController["Interaction"] = this.action;
If the default clip in the "Interaction" state was called "run" like in the first example above it should look like this:
myOverrideController["run"] = this.action;
From looking at the data structures in AnimatorOverrideController I realized that it doesn't keep track of the states or their names, only the clips themselves and what it will be overriding them with.
It's now working, thanks!
From what I understood the AnimatorOverrideController() changes a selected CLIP, not the assigned CLIP of a selected State. Is that possible?
Answer by MaDDoX · Feb 04, 2017 at 02:34 AM
@castor you can try something like the following - not literally, this is stripped from a class with other goals. It's a tad obnoxious (childState is not the same as a state?!?) but it's what the API allows us to do currently.
{
// Gets all states from currently selected layer
var layers = AnimatorController.layers;
var clipNames = new Dictionary<string, string>();
var childStates = layers[Layer].stateMachine.states;
foreach (var childState in childStates)
{
clipNames.Add(childState.state.name,childState.state.motion.name);
}
_animatorStates = Animator.GetStates();
StatesData.Clear();
foreach (var animatorState in _animatorStates)
{
StatesData.Add(new FsmState(animatorState.name, animatorState.nameHash, clipNames[animatorState.name]));
}
}
There does not seem to be .layers member in Animator class. I am bit confused what AnimatorController is...