Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Creating a BlendTree in a SubStateMachine Via API

Discussion in 'Animation' started by DDNA, Mar 16, 2017.

  1. DDNA

    DDNA

    Joined:
    Oct 15, 2013
    Posts:
    116
    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.
     
    UsefulYdyot likes this.
  2. TrickyHandz

    TrickyHandz

    Joined:
    Jul 23, 2010
    Posts:
    196
    @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.

    Code (CSharp):
    1.  
    2. // Copyright 2017 Timothy McDaniel
    3. //
    4. // Permission is hereby granted, free of charge, to any person obtaining a copy of
    5. // this software and associated documentation files (the "Software"), to deal in the
    6. // Software without restriction, including without limitation the rights to use, copy,
    7. // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
    8. // and to permit persons to whom the Software is furnished to do so, subject to the
    9. // following conditions:
    10. //
    11. // The above copyright notice and this permission notice shall be included in all copies
    12. // or substantial portions of the Software.
    13. //
    14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    15. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
    16. // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
    17. // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
    18. // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    19.  
    20. using UnityEngine;
    21. using UnityEditor;
    22. using UnityEditor.Animations;
    23.  
    24. public class MecanimAPIExample
    25. {
    26.     [MenuItem("Tools/Create Animator Example")]
    27.     static void CreateController()
    28.     {
    29.         // Create the animator in the project
    30.         AnimatorController animator = AnimatorController.CreateAnimatorControllerAtPath("Assets/Example.controller");
    31.         // For organization, we will create a new layer to work with
    32.         AnimatorControllerLayer exampleLayer = new AnimatorControllerLayer();
    33.         exampleLayer.name = "Example Layer";
    34.  
    35.         // The layer needs a new state machine as the base
    36.         AnimatorStateMachine layerStateMachine = new AnimatorStateMachine();
    37.         // Name the StateMachine using the layer name
    38.         layerStateMachine.name = exampleLayer.name;
    39.         // Save the statemachine as a child of the AnimatorControlelr
    40.         // asset so that it will be serialized properly
    41.         AssetDatabase.AddObjectToAsset(layerStateMachine, animator);
    42.         // Hide the statemachine asset in the animator's hierarchy
    43.         layerStateMachine.hideFlags = HideFlags.HideInHierarchy;
    44.         // Set the new layer's statemachine to the one that was created
    45.         exampleLayer.stateMachine = layerStateMachine;
    46.         // Now that everything is set up, add the layer to the
    47.         // AnimatorController so it can be used
    48.         animator.AddLayer(exampleLayer);
    49.  
    50.  
    51.         // Create a State for this layer to use
    52.         // This will become the layer's default state
    53.         AnimatorState exampleState = new AnimatorState();
    54.         exampleState.name = "Example State";
    55.         // Add the state to the state machine
    56.         // (The Vector3 is for positioning in the editor window)
    57.         layerStateMachine.AddState(exampleState, new Vector3(300, 0));
    58.         exampleLayer.stateMachine.defaultState = exampleState;
    59.  
    60.         // Now we will create a Sub-statemachine to add to the layer
    61.         AnimatorStateMachine exampleSubStateMachine = new AnimatorStateMachine();
    62.         exampleSubStateMachine.name = "Example SubStateMachine";
    63.         layerStateMachine.AddStateMachine(exampleSubStateMachine, new Vector3(300, 100));
    64.         // Now create a state for our Sub-statemachine...no sense
    65.         // in leaving it empty
    66.         AnimatorState exampleSubState = new AnimatorState();
    67.         exampleSubState.name = "Example SubStateMachine State";
    68.         exampleSubStateMachine.AddState(exampleSubState, new Vector3(100, 0));
    69.  
    70.         // Now to create a new blendtree. This works
    71.         // a little different than all the other states.
    72.         // First we declare a variable to store the
    73.         // BlendTree after is it created
    74.         BlendTree blendTree;
    75.         // Now we are going to create the BlendTree
    76.         // and add it to the last layer in the animator
    77.         animator.CreateBlendTreeInController("BlendTree Example", out blendTree, animator.layers.Length - 1);
    78.         // Hide the blendtree in asset hierarchy
    79.         blendTree.hideFlags = HideFlags.HideInHierarchy;
    80.         // In this case, we will make this a one
    81.         // dimensional BlendTree
    82.         blendTree.blendType = BlendTreeType.Simple1D;
    83.         // We will need a parameter for our BlendTree
    84.         // to sync with so we can add that now as well
    85.         string parameterName = "Example Parameter";
    86.         animator.AddParameter(parameterName, AnimatorControllerParameterType.Float);
    87.         // Set the blendParameter of our blend tree
    88.         // to the same name add our new parameter
    89.         blendTree.blendParameter = parameterName;
    90.         // Now to add a few motion fields to the
    91.         // BlendTree. These won't have clips, but
    92.         // you could actually add clips through
    93.         // code as well if you want to.
    94.         for (int i = 0; i < 3; i++)
    95.         {
    96.             blendTree.AddChild(null);
    97.         }
    98.  
    99.         // Now let's create a BlendTree a different way
    100.         // and this time add it to a SubStateMachine.
    101.         // I won't explain everything that has already
    102.         // been done...only the new stuff.
    103.         // We will create the substatemachine first.
    104.         AnimatorStateMachine subStateMachineWithBlendtree = new AnimatorStateMachine();
    105.         subStateMachineWithBlendtree.name = "SubStateMachine w/BlendTree";
    106.         layerStateMachine.AddStateMachine(subStateMachineWithBlendtree, new Vector3(500, 100));
    107.  
    108.         BlendTree subStateBlendtree = new BlendTree();
    109.         subStateBlendtree.name = "SubState BlendTree";
    110.         // Since we aren't using CreateBlendTreeInController()
    111.         // We need to add the object to the asset manually
    112.         // Just like we did earlier when creating the
    113.         // base statemachine for the layer
    114.         AssetDatabase.AddObjectToAsset(subStateBlendtree, animator);
    115.         subStateBlendtree.hideFlags = HideFlags.HideInHierarchy;
    116.         subStateBlendtree.blendType = BlendTreeType.Simple1D;
    117.         string substateBlendParameter = "Substate Blend";
    118.         animator.AddParameter(substateBlendParameter, AnimatorControllerParameterType.Float);
    119.         blendTree.blendParameter = substateBlendParameter;
    120.         for (int i = 0; i < 3; i++)
    121.         {
    122.             subStateBlendtree.AddChild(null);
    123.         }
    124.         // Now that we have created our blend tree
    125.         // and SubStateMachine we need to create a
    126.         // state to hold the blendtree since this
    127.         // was not added directly to a layer
    128.         AnimatorState blendTreeState = subStateMachineWithBlendtree.AddState("Blend Tree State");
    129.         // The BlendTree will be the motion for
    130.         // the state that we have created
    131.         blendTreeState.motion = subStateBlendtree;
    132.     }
    133. }
    134.  
    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
     
  3. SimplyTembak

    SimplyTembak

    Joined:
    Apr 6, 2016
    Posts:
    2
    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~!
     
    TrickyHandz likes this.
  4. LouskRad

    LouskRad

    Joined:
    Feb 18, 2014
    Posts:
    904
    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.
     
    TrickyHandz likes this.
  5. TrickyHandz

    TrickyHandz

    Joined:
    Jul 23, 2010
    Posts:
    196
    @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:

    Code (csharp):
    1.  
    2. // Copyright 2017 Timothy McDaniel
    3. //
    4. // Permission is hereby granted, free of charge, to any person obtaining a copy of
    5. // this software and associated documentation files (the "Software"), to deal in the
    6. // Software without restriction, including without limitation the rights to use, copy,
    7. // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
    8. // and to permit persons to whom the Software is furnished to do so, subject to the
    9. // following conditions:
    10. //
    11. // The above copyright notice and this permission notice shall be included in all copies
    12. // or substantial portions of the Software.
    13. //
    14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    15. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
    16. // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
    17. // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
    18. // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    19. using UnityEngine;
    20. using UnityEditor;
    21. using UnityEditor.Animations;
    22. public class MecanimAPIExamples
    23. {
    24.    
    25.     [MenuItem("Tools/Mecanim/Clean AnimatorController")]
    26.     public static void CleanAnimatorController()
    27.     {
    28.         AnimatorController controller = Selection.activeObject as AnimatorController;
    29.         // Get the path of the AnimatorController asset
    30.         // in the project.
    31.         string path = AssetDatabase.GetAssetPath(controller);
    32.         // Load all the assets that are found at the path
    33.         // of the controller. This list will include the
    34.         // controller itself and all the StateMachines and
    35.         // BlendTrees that are attached as subassets
    36.         Object[] assetList = AssetDatabase.LoadAllAssetsAtPath(path);
    37.         // Remove all the layers from the AnimatorController
    38.         for (int i = controller.layers.Length - 1; i >= 0; i--)
    39.         {
    40.             controller.RemoveLayer(i);
    41.         }
    42.         // Remove all the parameters from the controller
    43.         for (int i = controller.parameters.Length - 1; i >= 0; i--)
    44.         {
    45.             controller.RemoveParameter(i);
    46.         }
    47.         // Iterate all the asset that were found at
    48.         // the path of the controller
    49.         for (int i = 0; i < assetList.Length; i++)
    50.         {
    51.             Object target = assetList[i];
    52.             // If the asset isn't the controller itself,
    53.             // it can be destroyed. This will include all
    54.             // StateMachines, SubStateMachines, States, and
    55.             // Blendtrees referenced by the controller.
    56.             if (!AssetDatabase.IsMainAsset(target))
    57.             {
    58.                 Object.DestroyImmediate(assetList[i], true);
    59.             }
    60.         }
    61.         // Begin repopulating the controller
    62.         AnimatorControllerLayer baseLayer = new AnimatorControllerLayer();
    63.         baseLayer.name = "New Base Layer";
    64.         AnimatorStateMachine asm = new AnimatorStateMachine();
    65.         asm.name = "New Base ASM";
    66.         baseLayer.stateMachine = asm;
    67.         AssetDatabase.AddObjectToAsset(asm, controller);
    68.         controller.AddLayer(baseLayer);
    69.        
    70.         // Refresh the AssetDatabase
    71.         AssetDatabase.Refresh();
    72.     }
    73.  
    74.     [MenuItem("Tools/Mecanim/Clean AnimatorController", true)]
    75.     public static bool ValidateCleanAnimatorController()
    76.     {
    77.         Object selection = Selection.activeObject;
    78.         return selection != null && selection.GetType() == typeof(AnimatorController);
    79.     }
    80.  
    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
     
    Last edited: Jul 8, 2017
    twobob and LouskRad like this.
  6. LouskRad

    LouskRad

    Joined:
    Feb 18, 2014
    Posts:
    904
    @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
     
    TrickyHandz likes this.
  7. TrickyHandz

    TrickyHandz

    Joined:
    Jul 23, 2010
    Posts:
    196
    @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
     
  8. v2-Ton-Studios

    v2-Ton-Studios

    Joined:
    Jul 18, 2012
    Posts:
    238
    twobob likes this.