Creating a BlendTree in a SubStateMachine Via API

With the API is there a way to add a blendtree to a substate machine? Or move a state into a substate machine?

There is AnimatorController.CreateBlendTreeInController that creates a blend tree in the root. (it also makes the object this strange sub-object under the Controller in the project window). But there is no way that I have found to move the state into the sub state machine. If I just do new BlendTee() and set that as the motion on the State, it looks like it is setup correctly but if I click away or close unity it mystically disappears as if it was never there. If I create this and use AddState to try and rechild it I get a crash in unity about a key being added to a dictionary twice which completely corrupts the animation controller, forcing me to restore it from a previous version.

1 Like

@DDNA You have made some very interesting observations. I have spent a good deal of time with the Mecanim API and I can say from my experience that it can be quite confusing at times. I hope the following can give you some help with overcoming these difficulties.

Simple answer here is yes. You can add BlendTrees to a SubStateMachine and create States in SubStateMachines as well.

The reason the BlendTree is created as a child asset to the controller is totally because of how Mecanim works. Think of an Mecanim AnimatorController as a ScriptableObject asset. Each StateMachine, AnimatorState, Sub-StateMachine and BlendTree is set up as the same kind of asset and they are automatically added to the AnimatorController asset you are working with. This behaves exactly like calling AssetDatabase.AddObjectToAsset(Object obj, Object assetObject), except that it is built into the fuctions default behavior. You can hide those sub-assets by setting the HideFlags appropriately. As a note, you will have to use AssetDatabase.AddObjectToAsset() quite a bit if you are creating a complex AnimatorController though code.

This behavior is totally related to the need to use AssetDatabase.AddObjectToAssets() for proper serialization of the object.

I have put together an example for you to look at. I believe it covers all the use cases you have listed.

// Copyright 2017 Timothy McDaniel
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in the
// Software without restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies
// or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using UnityEngine;
using UnityEditor;
using UnityEditor.Animations;

public class MecanimAPIExample
{
    [MenuItem("Tools/Create Animator Example")]
    static void CreateController()
    {
        // Create the animator in the project
        AnimatorController animator = AnimatorController.CreateAnimatorControllerAtPath("Assets/Example.controller");
        // For organization, we will create a new layer to work with
        AnimatorControllerLayer exampleLayer = new AnimatorControllerLayer();
        exampleLayer.name = "Example Layer";

        // The layer needs a new state machine as the base
        AnimatorStateMachine layerStateMachine = new AnimatorStateMachine();
        // Name the StateMachine using the layer name
        layerStateMachine.name = exampleLayer.name;
        // Save the statemachine as a child of the AnimatorControlelr
        // asset so that it will be serialized properly
        AssetDatabase.AddObjectToAsset(layerStateMachine, animator);
        // Hide the statemachine asset in the animator's hierarchy
        layerStateMachine.hideFlags = HideFlags.HideInHierarchy;
        // Set the new layer's statemachine to the one that was created
        exampleLayer.stateMachine = layerStateMachine;
        // Now that everything is set up, add the layer to the
        // AnimatorController so it can be used
        animator.AddLayer(exampleLayer);


        // Create a State for this layer to use
        // This will become the layer's default state
        AnimatorState exampleState = new AnimatorState();
        exampleState.name = "Example State";
        // Add the state to the state machine
        // (The Vector3 is for positioning in the editor window)
        layerStateMachine.AddState(exampleState, new Vector3(300, 0));
        exampleLayer.stateMachine.defaultState = exampleState;

        // Now we will create a Sub-statemachine to add to the layer
        AnimatorStateMachine exampleSubStateMachine = new AnimatorStateMachine();
        exampleSubStateMachine.name = "Example SubStateMachine";
        layerStateMachine.AddStateMachine(exampleSubStateMachine, new Vector3(300, 100));
        // Now create a state for our Sub-statemachine...no sense
        // in leaving it empty
        AnimatorState exampleSubState = new AnimatorState();
        exampleSubState.name = "Example SubStateMachine State";
        exampleSubStateMachine.AddState(exampleSubState, new Vector3(100, 0));

        // Now to create a new blendtree. This works
        // a little different than all the other states.
        // First we declare a variable to store the
        // BlendTree after is it created
        BlendTree blendTree;
        // Now we are going to create the BlendTree
        // and add it to the last layer in the animator
        animator.CreateBlendTreeInController("BlendTree Example", out blendTree, animator.layers.Length - 1);
        // Hide the blendtree in asset hierarchy
        blendTree.hideFlags = HideFlags.HideInHierarchy;
        // In this case, we will make this a one
        // dimensional BlendTree
        blendTree.blendType = BlendTreeType.Simple1D;
        // We will need a parameter for our BlendTree
        // to sync with so we can add that now as well
        string parameterName = "Example Parameter";
        animator.AddParameter(parameterName, AnimatorControllerParameterType.Float);
        // Set the blendParameter of our blend tree
        // to the same name add our new parameter
        blendTree.blendParameter = parameterName;
        // Now to add a few motion fields to the
        // BlendTree. These won't have clips, but
        // you could actually add clips through
        // code as well if you want to.
        for (int i = 0; i < 3; i++)
        {
            blendTree.AddChild(null);
        }

        // Now let's create a BlendTree a different way
        // and this time add it to a SubStateMachine.
        // I won't explain everything that has already
        // been done...only the new stuff.
        // We will create the substatemachine first.
        AnimatorStateMachine subStateMachineWithBlendtree = new AnimatorStateMachine();
        subStateMachineWithBlendtree.name = "SubStateMachine w/BlendTree";
        layerStateMachine.AddStateMachine(subStateMachineWithBlendtree, new Vector3(500, 100));

        BlendTree subStateBlendtree = new BlendTree();
        subStateBlendtree.name = "SubState BlendTree";
        // Since we aren't using CreateBlendTreeInController()
        // We need to add the object to the asset manually
        // Just like we did earlier when creating the
        // base statemachine for the layer
        AssetDatabase.AddObjectToAsset(subStateBlendtree, animator);
        subStateBlendtree.hideFlags = HideFlags.HideInHierarchy;
        subStateBlendtree.blendType = BlendTreeType.Simple1D;
        string substateBlendParameter = "Substate Blend";
        animator.AddParameter(substateBlendParameter, AnimatorControllerParameterType.Float);
        blendTree.blendParameter = substateBlendParameter;
        for (int i = 0; i < 3; i++)
        {
            subStateBlendtree.AddChild(null);
        }
        // Now that we have created our blend tree
        // and SubStateMachine we need to create a
        // state to hold the blendtree since this
        // was not added directly to a layer
        AnimatorState blendTreeState = subStateMachineWithBlendtree.AddState("Blend Tree State");
        // The BlendTree will be the motion for
        // the state that we have created
        blendTreeState.motion = subStateBlendtree;
    }
}

If you wish to use any part of this code in a production project, I included the MIT license at the top. Let me know if this helps you out.

Cheers,
TrickyHandz

6 Likes

This post is a life saver.

We’ve started migrating our project over to defining animator controllers in script and came across the same problem.

We probably wouldn’t have figured out the ā€œAssetDatabase.AddObjectToAsset(subStateBlendtree, animator);ā€ part.

Thanks a bunch~!

1 Like

Awesome post, thanks for clarifying. I am using an editor script to rebuild the animator controller asset as we add new animations. However, since we don’t want prefabs to loose their reference, I am working on the same animator contoller asset, clearing its parameters and layers, then recreating them.

My problem is, if I don’t set hideFlags, I see that deleting the layer does not delete the blendtree objects under the animator controller, and each time I recreate my animator controller another blendtree object is added. How can I delete these objects but keep the animator controller asset?

Thanks again.

1 Like

@LouskRad there might be quite a few artifacts as well. I’ll have to check, but I’m fairly certain that States and StateMachines also remain as subassets despite the layer being deleted. Anyway, you have a rather interesting workflow and you can easily destroy all the subassets that exist on the controller. Here is a small script that should be able to make that all happen for you:

// Copyright 2017 Timothy McDaniel
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in the
// Software without restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies
// or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using UnityEngine;
using UnityEditor;
using UnityEditor.Animations;
public class MecanimAPIExamples
{
   
    [MenuItem("Tools/Mecanim/Clean AnimatorController")]
    public static void CleanAnimatorController()
    {
        AnimatorController controller = Selection.activeObject as AnimatorController;
        // Get the path of the AnimatorController asset
        // in the project. 
        string path = AssetDatabase.GetAssetPath(controller);
        // Load all the assets that are found at the path
        // of the controller. This list will include the
        // controller itself and all the StateMachines and
        // BlendTrees that are attached as subassets
        Object[] assetList = AssetDatabase.LoadAllAssetsAtPath(path);
        // Remove all the layers from the AnimatorController
        for (int i = controller.layers.Length - 1; i >= 0; i--)
        {
            controller.RemoveLayer(i);
        }
        // Remove all the parameters from the controller
        for (int i = controller.parameters.Length - 1; i >= 0; i--)
        {
            controller.RemoveParameter(i);
        }
        // Iterate all the asset that were found at
        // the path of the controller
        for (int i = 0; i < assetList.Length; i++)
        {
            Object target = assetList[i];
            // If the asset isn't the controller itself,
            // it can be destroyed. This will include all
            // StateMachines, SubStateMachines, States, and
            // Blendtrees referenced by the controller.
            if (!AssetDatabase.IsMainAsset(target))
            {
                Object.DestroyImmediate(assetList[i], true);
            }
        }
        // Begin repopulating the controller
        AnimatorControllerLayer baseLayer = new AnimatorControllerLayer();
        baseLayer.name = "New Base Layer";
        AnimatorStateMachine asm = new AnimatorStateMachine();
        asm.name = "New Base ASM";
        baseLayer.stateMachine = asm;
        AssetDatabase.AddObjectToAsset(asm, controller);
        controller.AddLayer(baseLayer);
        
        // Refresh the AssetDatabase
        AssetDatabase.Refresh();
    }

    [MenuItem("Tools/Mecanim/Clean AnimatorController", true)]
    public static bool ValidateCleanAnimatorController()
    {
        Object selection = Selection.activeObject;
        return selection != null && selection.GetType() == typeof(AnimatorController);
    }

Everything is run from a menu option, but you could easily incorporate this into a custom editor window as well. I hope this helps you out.

Cheers,
TrickyHandz

2 Likes

@TrickyHandz thank you so much for this. I integrated this to my workflow and it is working as intended now; this saved a lot of headache.

I strongly suggest the content of your answers to be added to the UnityEditor bit of the Scripting API section. This is very useful information indeed.

Thanks again,
Cheers

1 Like

@LouskRad Thanks so much for your kind words. I’m actually thinking about just putting together a GitHub Repo that can serve as a Mecanim API Toolbox as well as some other materials. Shoot me a PM sometime if you have a chance. I would love some input from someone that is actually automating their Mecanim workflow.

Cheers,
TrickyHandz

1 Like

Cross linking this here in case anyone finds it useful. It’s basically a way to update the ā€˜motion’ value of a sync’d layer’s blend trees. Unfourtunately it breaks ā€˜override’ which makes it useless to me. Perhaps someone can find where I’ve gone wrong and let me know.

1 Like

Hi how i can get the weight of each child of a blend tree the(influence to the interpolated motion)