Search Unity

[Mecanim] How do you modify blend trees in synced layers?

Discussion in 'Animation' started by dreasgrech, Apr 9, 2015.

  1. dreasgrech

    dreasgrech

    Joined:
    Feb 9, 2013
    Posts:
    205
    When syncing Mecanim layers, the synced states have no motion assigned to them. For normal states, this is good because I can drag and drop a new animation state, but how do you replace a blend tree in the synced layer?

    When selecting the blend tree on the synced layer, in the Inspector it shows you that there is no blend tree assigned to it. So then how do I change the animations in that blend tree?
     
    ModLunar, SiriAnimates and Gorov like this.
  2. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    Sync layer do not recreate all the blend tree hierarchy for you, because you can choose that for this sync layer this state is not a blend tree but only one clip.

    If you want to recreate a blend tree, you can right click on the state in the graph view and click on Create New Blend Tree in state or by script find out the original blend tree, make a copy and assign it to the sync layer state.
     
    ModLunar, MikeTon and dreasgrech like this.
  3. Gorov

    Gorov

    Joined:
    Feb 28, 2012
    Posts:
    10
    Can we get an example of copying the original blend tree please?

    UnityEditor.Animations.AnimatorControllerLayer.GetOverrideMotion returns the overridden motion but SetOverrideMotion does nothing.

    Thanks in advance!
     
    ModLunar likes this.
  4. Chaoseiro

    Chaoseiro

    Joined:
    Aug 28, 2013
    Posts:
    40
    Bumping since I'm interested in the method of copying the blend tree... I tried to manually change values in the controller file but Unity didn't like it...
    Maybe Blend Trees should also be saved as assets, so it would be easier to reuse/duplicate them...
     
    ModLunar likes this.
  5. v2-Ton-Studios

    v2-Ton-Studios

    Joined:
    Jul 18, 2012
    Posts:
    238
    A hail mary bump for a script example of how to do this.

    "... or by script find out the original blend tree, make a copy and assign it to the sync layer state."

    Not being able to do this makes layers unusable for me :|.
     
    ModLunar and twobob like this.
  6. v2-Ton-Studios

    v2-Ton-Studios

    Joined:
    Jul 18, 2012
    Posts:
    238
    Here's where I got. Perhaps @Mecanim-Dev can tell me where I've gone wrong.

    The below successfully fixed the non-copying / syncing blendtrees in the new layer. But, ultimately doesn't solve the problem b/c it syncs them while breaking override. Meaning, that if I update a blend tree on layer 1, it updates the layer 0 blend tree. Is there a way around this?

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using System.Collections;
    5. using System.Reflection;
    6. using UnityEditor.Animations;
    7.  
    8. public class DuplicateAnimatorLayer : EditorWindow
    9. {
    10. UnityEditor.Animations.AnimatorController source;
    11.     string layerToCopy_Value;
    12.     int layerToFix;
    13.  
    14. [MenuItem("2Ton/Duplication Animator Layer")]
    15. static void GetWindow()
    16. {
    17. EditorWindow.GetWindow(typeof(DuplicateAnimatorLayer));
    18. }
    19.  
    20. void OnGUI()
    21. {
    22. source = EditorGUILayout.ObjectField("Source", source, typeof(AnimatorController),
    23.             true, GUILayout.ExpandWidth(true)) as AnimatorController;
    24.  
    25. layerToFix = EditorGUILayout.IntField("Layer to Fix: ", layerToFix);
    26.  
    27. // EditorGUILayout.TextField("Object Name: ", Selection.activeGameObject.name);
    28.  
    29. if (GUILayout.Button("Copy", GUILayout.ExpandWidth(true), GUILayout.Height(24)))
    30. {
    31. if (source != null && layerToFix != 0)
    32.             {
    33. copy();
    34.             }
    35. }
    36. }
    37.  
    38.     void copy()
    39.     {
    40.         AnimatorStateMachine baseStateMachine = source.layers[0].stateMachine;
    41.  
    42. // as per unity documentation: https://docs.unity3d.com/ScriptReference/Animations.AnimatorController-layers.html
    43. // "It's important to note that the AnimatorControllerLayer are returned as a copy. The array should be set back into the property when changed."
    44.         // so we need to explicitly get the controller's layers, then below we update their motions, then we explicitly update them
    45. AnimatorControllerLayer[] layers = source.layers;  
    46.  
    47. // go through all the sub-state machines
    48. for (int i = 0; i < baseStateMachine.stateMachines.Length; i++)
    49. {
    50.             AnimatorStateMachine stateMachine = baseStateMachine.stateMachines[i].stateMachine;
    51.  
    52.             // update motions
    53. for (int m = 0; m < stateMachine.states.Length; m++)
    54. {
    55.                 // we only care about blend trees, they are the ones that don't sync
    56. if (stateMachine.states[m].state.motion.GetType() == typeof(BlendTree))
    57. {
    58. AnimatorState state = stateMachine.states[m].state;
    59. UnityEngine.Motion motion = layers[layerToFix].GetOverrideMotion(state);
    60.  
    61. if (motion == null)
    62. {
    63. Debug.Log("Overriding: " + state.name);
    64. layers[layerToFix].SetOverrideMotion(state, state.motion);
    65. }
    66. }
    67.             }
    68.  
    69.             // explicitly update the controller's layers so the copy gets updated
    70.             source.layers = layers;
    71. }
    72.     }
    73.  
    74.     // I wish this worked...
    75.  
    76.     // if (stateMachine.states[m].state.motion.GetType() == typeof(BlendTree))
    77.     // {                  
    78.     //  AnimatorState state = stateMachine.states[m].state;
    79.     //  UnityEngine.Motion motion = layers[layerToFix].GetOverrideMotion(state);
    80.  
    81.     //  if (motion == null)
    82.     //  {
    83.     //      BlendTree baseBlendTree = state.motion as BlendTree;
    84.     //      BlendTree newBlendTree = new BlendTree();
    85.  
    86.     //      SerializedObject SO = new SerializedObject(newBlendTree);
    87.     //      SerializedProperty SP = SO.GetIterator();
    88.  
    89.     //      SP = SO.FindProperty("m_UseAutomaticThresholds");
    90.     //      SP.boolValue = false;
    91.     //      SO.ApplyModifiedProperties();
    92.  
    93.     //      newBlendTree.blendParameter = baseBlendTree.blendParameter;
    94.     //      newBlendTree.blendType = baseBlendTree.blendType;
    95.  
    96.     //      for (int j = 0; j < baseBlendTree.children.Length; j++)
    97.     //      {
    98.     //          newBlendTree.AddChild(baseBlendTree.children[j].motion, baseBlendTree.children[j].threshold);
    99.     //      }
    100.  
    101.     //      for (int j = 0; j < baseBlendTree.children.Length; j++)
    102.     //      {
    103.     //          UnityEditor.Animations.ChildMotion[] newChildren = newBlendTree.children;
    104.     //          newChildren[j].timeScale = baseBlendTree.children[j].timeScale;
    105.     //          newChildren[j].position = baseBlendTree.children[j].position;
    106.     //          newChildren[j].threshold = baseBlendTree.children[j].threshold;
    107.     //          newChildren[j].directBlendParameter = baseBlendTree.children[j].directBlendParameter;
    108.     //          newBlendTree.children = newChildren;
    109.     //      }
    110.  
    111.     //      motion = newBlendTree;
    112.  
    113.     //      Debug.Log("Overriding: " + state.name);
    114.     //  }
    115.     // }
    116. }
    117.  
     
    Last edited: Mar 4, 2018
  7. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    They can be saved as asset and reused.

    This is not something that a lot of user are aware of but statemachine and blend tree are asset that can be saved. In the normal editor workflow they are embedded into the controller asset.
    But it also have a side effect if you share a blend tree it does mean that if you change it, it will also change all other reference. That exactly what @v2-Ton-Studios is experiencing.

    The best way to create a duplicate of the original blend tree is to use Object.Instantiate
    https://docs.unity3d.com/ScriptReference/Object.Instantiate.html
     
  8. v2-Ton-Studios

    v2-Ton-Studios

    Joined:
    Jul 18, 2012
    Posts:
    238
    I'll dig into this in a few hours, but are you saying that if I use Instantiate in my above code, parameterize so it matches the "base blend tree", and then pass it in as
    Code (csharp):
    1. SetOverrideMotion (state, instantiatedBlendTree);
    ... it should work i.e. it would be a true copy, not a reference?
     
    ModLunar likes this.
  9. v2-Ton-Studios

    v2-Ton-Studios

    Joined:
    Jul 18, 2012
    Posts:
    238
    Had to try this now... :D. Using Instantiate makes no difference...

    Code (csharp):
    1.  
    2. if (motion == null)
    3. {
    4.     BlendTree baseBlendTree = state.motion as BlendTree;
    5.     BlendTree newBlendTree = Instantiate <BlendTree> (baseBlendTree);
    6.  
    7.     // feels like this is the issue. SetOverride "syncs" the blendtrees?
    8.     layers[layerToFix].SetOverrideMotion(state, newBlendTree);
    9.  
    10.     Debug.Log("Overriding: " + state.name);
    11. }
    12.  
    What am I missing here?

    The above still results in what seems like a reference between baseBlendTree "Layer0.Foo" and newBlendTree "Layer1.Foo"... :(.

    TIA!
     
  10. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    hard to tell without debugging the code, I can tell you that we have unit test covering this area and it does work so there is something specific to you're project.

    One thing you can do is force your asset serialization to text mode, then open up you're controller asset file and look at what the file look like before and after you're operation.

    Each object serialized into the file have a unique ID, something like this:
    --- !u!91 &9100000
    AnimatorController:


    Find the animator state that you are trying to edit, you should have two: one for the base layer and one for the sync layer and check for m_Motion field
    --- !u!1102 &1102000010750584734
    AnimatorState:
    ...
    m_Motion: {fileID: 7400000, guid: 1d531f81c90784a79b0591821aa8875d, type: 2}

    the two animator state should have a unique id for the motion, if they are the same then it does mean that they reference the same object.

    or you could you log a bug with you're script ? we will investigate
     
    twobob likes this.
  11. v2-Ton-Studios

    v2-Ton-Studios

    Joined:
    Jul 18, 2012
    Posts:
    238
    Okay good to know that it should work.

    I'll investigate a bit more and see what I find, file a bug if needed. Thanks.
     
  12. Dawdlebird

    Dawdlebird

    Joined:
    Apr 22, 2013
    Posts:
    88
    How? They don't show up as assets in the asset folder, nor is there any "save as" option as far as I can see for blendtrees.
     
  13. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    ModLunar, twobob and Dawdlebird like this.
  14. Dawdlebird

    Dawdlebird

    Joined:
    Apr 22, 2013
    Posts:
    88
  15. Dawdlebird

    Dawdlebird

    Joined:
    Apr 22, 2013
    Posts:
    88
    Ok, I've written a quick script to make a duplicate of a selected blendtree and save it as an asset, as Mecanim-Dev suggested. Make sure you actually have a blendtree selected (for the Selection.activeObject as Blendtree to work).

    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3. using UnityEditor.Animations;
    4.  
    5. public class CreateBlendtreeAsset : MonoBehaviour {
    6.  
    7.     [MenuItem("AnimTools/GameObject/Create Blendtree")]
    8.     static void CreateBlendtree()
    9.     {
    10.      
    11.         BlendTree BT = Selection.activeObject as BlendTree;
    12.  
    13.         BlendTree BTcopy = Instantiate<BlendTree>(BT);
    14.         AssetDatabase.CreateAsset(BTcopy,  ("Assets/" + BT.name + ".asset"));
    15.     }
    16. }
    17.  
     
    nirvanajie and twobob like this.
  16. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    ModLunar and twobob like this.
  17. Dawdlebird

    Dawdlebird

    Joined:
    Apr 22, 2013
    Posts:
    88
    Like this you mean?
    Code (CSharp):
    1. public class CreateBlendtreeAsset : MonoBehaviour {
    2.  
    3.     [MenuItem("AnimTools/GameObject/Asset from Blendtree")]
    4.     static void CreateBlendtree()
    5.     {
    6.      
    7.         BlendTree BT = Selection.activeObject as BlendTree;
    8.  
    9.         BlendTree BTcopy = Instantiate<BlendTree>(BT);
    10.  
    11.         AssetDatabase.CreateAsset(BTcopy, AssetDatabase.GenerateUniqueAssetPath("Assets/" + BT.name + ".asset"));
    12.     }
    13. }
    [edit] earlier mentioned error seemed to have vanished after windows restart. So never mind that.
     
    Last edited: Apr 19, 2018
    nirvanajie and twobob like this.
  18. twobob

    twobob

    Joined:
    Jun 28, 2014
    Posts:
    2,058
    Last edited: Nov 3, 2018
  19. v2-Ton-Studios

    v2-Ton-Studios

    Joined:
    Jul 18, 2012
    Posts:
    238
    Here's a GUI version that works pretty well. Select a state in your animator, the script will find the BlendTree and select it for you based on the state's "motion" value.

    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3. using UnityEditor.Animations;
    4.  
    5. public class CreateBlendtreeAsset : EditorWindow
    6. {
    7.     BlendTree _blendTree;
    8.     AnimatorState _animatorState;
    9.  
    10.     string _motionName, _newName, _createPath;
    11.  
    12.     [MenuItem("Window/Duplicate Blendtree")]
    13.     public static void ShowWindow()
    14.     {
    15.         //Show existing window instance. If one doesn't exist, make one.
    16.         EditorWindow.GetWindow(typeof(CreateBlendtreeAsset));
    17.     }
    18.  
    19.     void OnGUI()
    20.     {
    21.         GUILayout.Label("Base Settings", EditorStyles.boldLabel);
    22.  
    23.         EditorGUILayout.BeginHorizontal();
    24.         GUILayout.Label("BlendTree");      
    25.         _blendTree = EditorGUILayout.ObjectField(_blendTree, typeof(BlendTree), true) as BlendTree;
    26.         EditorGUILayout.EndHorizontal();
    27.  
    28.         if (_blendTree)
    29.         {
    30.             EditorGUILayout.BeginHorizontal();
    31.             GUILayout.Label("BlendTree");
    32.             EditorGUILayout.LabelField(_motionName);
    33.             EditorGUILayout.EndHorizontal();
    34.  
    35.             EditorGUILayout.BeginHorizontal();
    36.             GUILayout.Label("New Name");
    37.             _newName = EditorGUILayout.TextField(_newName);
    38.             EditorGUILayout.EndHorizontal();
    39.  
    40.             EditorGUILayout.BeginHorizontal();
    41.             GUILayout.Label("Create Path");
    42.             _createPath = EditorGUILayout.TextField(_createPath);
    43.             EditorGUILayout.EndHorizontal();
    44.  
    45.             if (GUILayout.Button("Duplicate BlendTree"))
    46.             {
    47.                 int canRun = 0;
    48.  
    49.                 if (_newName == null || _newName == "")
    50.                 {
    51.                     ShowNotification(new GUIContent("Provide a Name for BlendTree"));
    52.                 }
    53.                 else
    54.                 {
    55.                     canRun++;
    56.                 }
    57.  
    58.                 if (_createPath == null || _createPath == "")
    59.                 {
    60.                     ShowNotification(new GUIContent("Provide a path for BlendTree"));
    61.                 }
    62.                 else
    63.                 {
    64.                     canRun++;
    65.                 }
    66.  
    67.                 if (canRun == 2)
    68.                 {
    69.                     BlendTree BTcopy = Instantiate<BlendTree>(_blendTree);
    70.  
    71.                     AssetDatabase.CreateAsset(BTcopy, AssetDatabase.GenerateUniqueAssetPath(_createPath + _newName + ".asset"));
    72.                 }
    73.             }
    74.         }
    75.         else
    76.         {
    77.             setBlendTree();
    78.         }  
    79.     }
    80.  
    81.     void Update ()
    82.     {
    83.         setBlendTree();
    84.     }
    85.  
    86.     private void setBlendTree()
    87.     {
    88.         AnimatorState AS = Selection.activeObject as AnimatorState;
    89.  
    90.         if (AS != null && AS != _animatorState && AS.motion is BlendTree)
    91.         {
    92.             _animatorState = AS;
    93.             _motionName = _animatorState.name;
    94.  
    95.             _blendTree = _animatorState.motion as BlendTree;
    96.         }
    97.     }
    98. }
    99.  
     
    Last edited: Aug 2, 2019
  20. Dawdlebird

    Dawdlebird

    Joined:
    Apr 22, 2013
    Posts:
    88
    Nice, will give it a try!
     
  21. v2-Ton-Studios

    v2-Ton-Studios

    Joined:
    Jul 18, 2012
    Posts:
    238
    Sadly coming back to this, again... :|... this only works for top level BlendTrees... the ones nested under sub-state machines.

    BlendTrees as values within BlendTrees don't seem to be copy'able AND they seem to be passed by reference not value.

    Concrete example...

    BlendTree: Run

    --> Backwards (Motion)
    --> Forward (BlendTree)
    --> Sneak (Motion)
    --> Normal (BlendTree)
    --> Incline (Motion)
    --> Flat (Motion)
    --> Decline (Motion)​
    --> Wounded (Motion)
    Forward and Normal, highlighted above appear to be passed by reference... if I make a new animator layer 'Torch', I cannot override layer Base > Normal > Flat with layer Torch > Normal > Flat_Torch because replacing 'Flat' with 'Flat_Torch' causes the Flat "slot" to be updated in all copies of the BlendTree.

    I also don't seem to be able to separately copy the nested BlendTrees (Forward and Normal) in the above example.

    1, What am I missing here?

    2, Is there a "deep copy" I can do of a BlendTree?

    3, What's the rationale for making animator layers so difficult to use with BlendTrees?

    Thanks in advance,

    S
     
  22. GarveySoftware

    GarveySoftware

    Joined:
    Sep 28, 2018
    Posts:
    6
    I made an editor extension that encapsulates a lot of the craziness you are experiencing here. It has support for duplicating and editing blend trees in synced states, state machines and layers with undo/redo. It displays the entire animator controller hierarchy as a tree view and even has drag/drop support for rearranging blend trees and animations.

    Saving blend trees as an asset in the project has a ton of really nasty side effects, I'm not even sure what the runtime would do if you added the same blend tree to multiple states in an animator controller. There are also other issues like parameters potentially not matching between blend tree assets and controllers they are being added to. I suspect this is why it isn't something that can be done through the Unity UI by default. Deep copying of blend trees really has to be done by hand one property at time, recursively populating children, and you have to be careful with the asset book keeping especially when dealing with synced layers.

    The code for this is all available in my asset if you want to check it out here: https://assetstore.unity.com/packages/tools/animation/anim-tree-editor-161217
     
    Last edited: Jan 30, 2020
    Lorrak and WendelinReich like this.
  23. MOBILIN

    MOBILIN

    Joined:
    May 6, 2017
    Posts:
    15
    Would you let me know how to do this via editor?
    I want to assign motions in which I created a blentree in sync layer.

    I know how to create and assign a blentree in another layer such as Base layer,
    but i don't know how I can assign a blendtree in sync layer.

    Doc does not explain enough about this and I cannot find any information on any forum.
    This is the most pain point on game development using Unity. No information...

    Why do we have to find any Unity development information on Internet? Shouldn't it be possible to browse the Unity doc and be able to find any just like Microsoft does?
     
    Last edited: Jan 27, 2022