Search Unity

Transition from all states inside sub-state machine

Discussion in 'Animation' started by GarlicDipping, Nov 5, 2016.

  1. GarlicDipping

    GarlicDipping

    Joined:
    Sep 7, 2013
    Posts:
    33
    Hi, I've been refactoring my complex animator controller recently, and faced some problems using sub-state machine.

    Here's my example screenshot :



    I just need to come and go between "Fall" and "GroundMotions" substatemachines, with 'grounded' bool condition. Whatever state is playing inside sub state machine, when condition is right I want to cancel it and transit between each other.

    But, to do this, it seems like I have to add transitions for ALL states inside each substatemachine...

    I know there's "Any State" feature, but there's no substatemachine specific "Any State" yet.

    So, are there no options other than just use Animator.Play in onStateUpdate()?

    Thanks for your answer in advance!
     
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    That would be so nice. Sadly, no can do without scripting.
     
    alecmak likes this.
  3. GarlicDipping

    GarlicDipping

    Joined:
    Sep 7, 2013
    Posts:
    33
    Thanks for the answer. Wow, well, that's...so...sad... :(
     
    alecmak likes this.
  4. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    Have you try with Entry and Exit state?

    At least with entry and exit node when you add a new state you don't need to add a transition for all possible permutation
     
  5. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    They're already using the exit/entry nodes (see the screenshot, the arrow's gray, which indicates a machine transition).
     
  6. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    ah true, I didn't spot it the first time
     
  7. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    So what do you think about an "any internal state" node, which works like the any-state node, but only as long as you're inside of the substatemachine the node's in?

    Setups like @GarlicDipping's, where you want to exit a machine no matter where you're in it, are pretty common. an Any Internal State node would help a lot. It'd also reduce the number of transitions and make the state machines much more appealing and readable.
     
    Glutax, Ducaticoder, RemDust and 2 others like this.
  8. GarlicDipping

    GarlicDipping

    Joined:
    Sep 7, 2013
    Posts:
    33
    Yup, as @Baste says, 'Any internal state' node will be really helpful. I found there's already feedback here, but seems like it's not on the spotlight yet...
     
    RemDust likes this.
  9. IgorAherne

    IgorAherne

    Joined:
    May 15, 2013
    Posts:
    393
    OMG @Mecanim-Dev plz implement it guys!

    It's needed, and would improve the mecanim workflow a lot, its usability is very high
     
  10. fup

    fup

    Joined:
    Jan 18, 2016
    Posts:
    76
    yup, this feature would be extremely handy
     
  11. IgorAherne

    IgorAherne

    Joined:
    May 15, 2013
    Posts:
    393
    I agree, - even though it would mean having to check more outgoing transitions, to see what the user intended to do. Who cares, just do it guys!! it will be amazing
     
  12. homer_3

    homer_3

    Joined:
    Jun 19, 2011
    Posts:
    111
    Would still love to have this. Hopefully it gets added.
     
  13. kyuskoj

    kyuskoj

    Joined:
    Aug 28, 2013
    Posts:
    56
    no more updates? we need this feature so much.
     
    alecmak likes this.
  14. azevedco

    azevedco

    Joined:
    Mar 2, 2014
    Posts:
    34
    Heh...Jumping onto this bandwagon too. A node for 'Any internal state' would be beautiful.
     
  15. Tiernan98

    Tiernan98

    Joined:
    Jul 11, 2017
    Posts:
    42
    Just saw this thread when I was googling. Have so many situations like this in my game. Currently, I am calling Play/CrossFade on the animator in the player controller's state machine (not the Mecanim one). It would be very neat to have some default state within a sub-state machine that you can transition to.
     
  16. io-games

    io-games

    Joined:
    Jun 2, 2016
    Posts:
    104
    +1 for "any internal state".
    I need to exit to parent state from all of these.
    upload_2021-1-18_18-28-41.png
     
    marcospgp likes this.
  17. marcospgp

    marcospgp

    Joined:
    Jun 11, 2018
    Posts:
    194
    Is the Unity team aware of this? Would be really useful.

    Right now the only way to do something similar is to add a transition out of the global Any State node, which doesn't always fit with what one wants to do, or adding transitions from each sub state machine node to the Exit node.
     
    alecmak and Tiernan98 like this.
  18. carcasanchez

    carcasanchez

    Joined:
    Jul 15, 2018
    Posts:
    177
    I'm in the same situation. It feels ridiculous Unity has been aware of this since 2016, and you have still to do a one-by-one transition when working with sub-state machines.
     
    Tiernan98 likes this.
  19. hyagogow

    hyagogow

    Joined:
    Apr 25, 2014
    Posts:
    26
    I'd really appreciate this feature as well.
     
    adamgryu, bluescrn and marcospgp like this.
  20. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,278
    Agreed, this would be very very useful
     
  21. vandervillain

    vandervillain

    Joined:
    Feb 10, 2016
    Posts:
    1
    yeah would love it---bump
     
  22. GodModeTech

    GodModeTech

    Joined:
    Jun 21, 2010
    Posts:
    66
    Are we never getting this? It would be so useful.
     
  23. williamborgo

    williamborgo

    Joined:
    Aug 19, 2018
    Posts:
    3
    I landed here searching for this feature in google, it's so sad that it does't exists.

    I have this 6 blend trees for my movement, need to transit it to "Jump", "Fall", "AtackFork", "Dash", "Block", etc
    If I have just 5 States to transit, I need to configure 30 transitions, and mostly is almost a copy, so if I need to change junt one parameter, I will need to change in all the 6 sub state transition.

    It's a nightmare
     

    Attached Files:

    Last edited: Jun 15, 2022
  24. HamCha87

    HamCha87

    Joined:
    Jan 4, 2019
    Posts:
    7
    WE NEEEDDD IT!!! bump
     
    alecmak likes this.
  25. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    I was looking for a way to do this in unity but after coming here it looks like its unsupported so I wrote my own StateMachineBehaviour script that would handle this.

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3. using System;
    4.  
    5. #if UNITY_EDITOR
    6. using System.Reflection;
    7. using System.Linq;
    8. using UnityEditor.Animations;
    9. using AnimatorController = UnityEditor.Animations.AnimatorController;
    10. #endif
    11.  
    12.     public class AnySubstateTranistionController_SMB : StateMachineBehaviour
    13.     {
    14.  
    15.  
    16.         [SerializeField][HideInInspector] private TransitionInfo[] bakedTransitions;
    17.         bool inTransition;
    18.  
    19. #if UNITY_EDITOR
    20.         [SerializeField] private List<TransitionConfig> config = new List<TransitionConfig>();
    21.  
    22.         public void OnValidate() => BakeTransitions();
    23.  
    24.         //for some reason the needed function to grab the direct parent is internal so need to use reflection to find it
    25.         private static AnimatorStateMachine FindParentStateMacihine(AnimatorStateMachine root,AnimatorStateMachine child)
    26.         {
    27.             var type = typeof(AnimatorStateMachine);
    28.             var method_FindParent = type.GetMethod("FindParent", BindingFlags.Instance | BindingFlags.NonPublic);
    29.             var parent = (AnimatorStateMachine)method_FindParent.Invoke(root,new object[] { child});
    30.             return parent;
    31.         }
    32.  
    33.         public void BakeTransitions()
    34.         {
    35.  
    36.             var map = config.ToDictionary(c => c.editorReference, c => c.transition);
    37.             var newMap = new Dictionary<AnimatorTransition, TransitionInfo>();
    38.  
    39.             var contexts = AnimatorController.FindStateMachineBehaviourContext(this);
    40.             foreach (var c in contexts)
    41.             {
    42.                 if (!(c.animatorObject is AnimatorStateMachine asm))
    43.                 {
    44.                     continue;
    45.                 }
    46.                 var rootMachine = c.animatorController.layers[c.layerIndex].stateMachine;
    47.                 var parent = FindParentStateMacihine(rootMachine, asm);
    48.  
    49.                 var allExitTransitions = parent.GetStateMachineTransitions(asm);
    50.  
    51.                 foreach(var t in allExitTransitions)
    52.                 {
    53.                     if (!map.TryGetValue(t, out var oldInfo))
    54.                     {
    55.                         oldInfo = TransitionInfo.Create(t, c.animatorController, c.layerIndex);
    56.                     }
    57.                     else
    58.                     {
    59.                         oldInfo = TransitionInfo.Create(t, c.animatorController, c.layerIndex,oldInfo.crossfadeDuration);
    60.                     }
    61.  
    62.                     if(oldInfo.conditons.Length >0)
    63.                     {
    64.                         oldInfo.name = t.GetDisplayName(asm);
    65.                         newMap[t] = oldInfo;
    66.                     }
    67.                 }
    68.             }
    69.  
    70.             config = newMap.Select(kvp=> new TransitionConfig() {name=kvp.Value.name, editorReference = kvp.Key, transition = kvp.Value})
    71.                 .ToList();
    72.  
    73.             bakedTransitions = newMap.Values.ToArray();
    74.  
    75.         }
    76.     #endif
    77.  
    78.         #region StateMachineBehaviour
    79.         override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    80.         {
    81.             if (inTransition)
    82.             {
    83.                 inTransition = false;
    84.             }
    85.         }
    86.  
    87.         override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    88.         {
    89.             if (inTransition)
    90.             {
    91.                 return;
    92.             }
    93.  
    94.             for (int i = 0; i < bakedTransitions.Length; i++)
    95.             {
    96.                 var info = bakedTransitions[i];
    97.  
    98.                 if (info.TryTransition(animator, stateInfo, false, layerIndex))
    99.                 {
    100.                     inTransition = true;
    101.  
    102.                     return;
    103.                 }
    104.  
    105.             }
    106.         }
    107.         #endregion
    108.  
    109.         #region typedefs
    110. #if UNITY_EDITOR
    111.         [Serializable]
    112.         private class TransitionConfig
    113.         {
    114.             [HideInInspector] public string name;// hiding field will hide it on the element ,but will appear as the summary title when TransitionConfig is in an array in the inspector
    115.             [HideInInspector]public AnimatorTransition editorReference;
    116.             public TransitionInfo transition;
    117.         }
    118. #endif
    119.         [Serializable]
    120.         private struct TransitionInfo
    121.         {
    122.             [HideInInspector]public string name;
    123.             [HideInInspector]public int destinationStateHash;
    124.             public float crossfadeDuration;
    125.             public TransitionCondition[] conditons;
    126.  
    127.     #if UNITY_EDITOR
    128.             public static TransitionInfo Create(AnimatorTransition data,AnimatorController controller,int layer, float crossfadeDuration = 0.25f)
    129.             {
    130.                 var targetState = data.destinationState?? data.destinationStateMachine?.defaultState ?? controller.layers[layer].stateMachine.defaultState;
    131.  
    132.                 var conditions = data.conditions.Select(c=> TransitionCondition.Create(c, controller));
    133.  
    134.                 return new TransitionInfo()
    135.                 {
    136.                     destinationStateHash = targetState.nameHash,
    137.                     crossfadeDuration = crossfadeDuration,
    138.                     conditons = conditions.ToArray()
    139.                 };
    140.             }
    141.     #endif
    142.  
    143.             public bool TryTransition(Animator animator, AnimatorStateInfo stateInfo,bool isExit, int layer)
    144.             {
    145.                 if (stateInfo.fullPathHash == destinationStateHash) return false;
    146.  
    147.                 for(int i=0;i< conditons.Length;i++)
    148.                 {
    149.  
    150.                     if (!conditons[i].Evaluate(animator)) return false;
    151.  
    152.  
    153.                 }
    154.  
    155.                 if(conditons.Length < 1 && !isExit)
    156.                     return false;
    157.      
    158.  
    159.                 for(int i=0;i<conditons.Length;i++)
    160.                     conditons[i].Consume(animator);
    161.  
    162.                 animator.CrossFadeInFixedTime(destinationStateHash, crossfadeDuration, layer);
    163.                 return true;
    164.             }
    165.         }
    166.  
    167.         [Serializable]
    168.         private struct TransitionCondition
    169.         {
    170.             /// <summary>
    171.             /// the type of condition to eval, this is a mirroe of UnityEditor.Animations.AnimatorConditionMode
    172.             /// </summary>
    173.             public enum Condition
    174.             {
    175.                 If = 1,
    176.                 IfNot = 2,
    177.                 Greater = 3,
    178.                 Less = 4,
    179.                 Equals = 6,
    180.                 NotEqual = 7
    181.             }
    182.             public enum DataType { Float=1, Int=3, Bool=4, Trigger=9 }
    183.  
    184.             [HideInInspector]public DataType dataType;
    185.             [HideInInspector] public int paramHash;
    186.             [Tooltip("This field cannot be edited, to change this condition edit the original transition then try to update this behaviour")]
    187.             public string parameter;
    188.             [Tooltip("This field cannot be edited, to change this condition edit the original transition then try to update this behaviour")]
    189.             public Condition mode;
    190.             [Tooltip("This field cannot be edited, to change this condition edit the original transition then try to update this behaviour")]
    191.             public float threshold;
    192.  
    193.         #if UNITY_EDITOR
    194.             public static TransitionCondition Create(AnimatorCondition data, AnimatorController editorAnimator)
    195.             {
    196.                 var parameters = editorAnimator.parameters;
    197.                 AnimatorControllerParameter parameter = default;
    198.                 for(int i =0;i< parameters.Length;i++ )
    199.                 {
    200.                     if(parameters[i].nameHash == Animator.StringToHash(data.parameter))
    201.                     {
    202.                         parameter = parameters[i];
    203.                         break;
    204.                     }
    205.                 }
    206.  
    207.                 return new TransitionCondition()
    208.                 {
    209.                     dataType = (DataType)parameter.type,
    210.                     paramHash = parameter.nameHash,
    211.                     parameter = parameter.name,
    212.                     mode = (Condition)data.mode,
    213.                     threshold = data.threshold
    214.                 };
    215.             }
    216.         #endif
    217.  
    218.  
    219.             public bool Evaluate(Animator animator)
    220.             {
    221.                 switch(dataType)
    222.                 {
    223.                     case DataType.Float:
    224.                         var floatValue = animator.GetFloat(paramHash);
    225.                         switch(mode)
    226.                         {
    227.                             case Condition.Greater:
    228.                                 return floatValue > threshold;
    229.                             case Condition.Less:
    230.                                 return floatValue < threshold;
    231.                             case Condition.Equals:
    232.                                 return floatValue == threshold;
    233.                             case Condition.NotEqual:
    234.                                 return floatValue != threshold;
    235.  
    236.                             default: return false;
    237.  
    238.                         }
    239.                     case DataType.Int:
    240.                         var intValue = animator.GetInteger(paramHash);
    241.                         switch (mode)
    242.                         {
    243.                             case Condition.Greater:
    244.                                 return intValue > threshold;
    245.                             case Condition.Less:
    246.                                 return intValue < threshold;
    247.                             case Condition.Equals:
    248.                                 return intValue == threshold;
    249.                             case Condition.NotEqual:
    250.                                 return intValue != threshold;
    251.  
    252.                             default: return false;
    253.  
    254.                         }
    255.                     case DataType.Bool:
    256.                         var boolValue = animator.GetBool(paramHash);
    257.                         switch (mode)
    258.                         {
    259.                             case Condition.If:
    260.                                 return boolValue;
    261.                             case Condition.IfNot:
    262.                                 return !boolValue;
    263.  
    264.                             default:
    265.                                 return false;
    266.  
    267.                         }
    268.                     case DataType.Trigger:
    269.                         var triggerValue = animator.GetBool(paramHash); // Triggers can be "read" as bool parameters
    270.                         return triggerValue;
    271.  
    272.                     default: return false;
    273.                 }
    274.             }
    275.  
    276.             public void Consume(Animator animator)
    277.             {
    278.                 if (dataType == DataType.Trigger)
    279.                 {
    280.                     animator.ResetTrigger(paramHash);
    281.                 }
    282.             }
    283.         }
    284.         #endregion
    285.     }
    286.  

    You place this on the statemachine parent and then instead of running all your transitions from an anystate inside that sub-statemachine node, you run it from this parent node. That statemachine will now suddenly behave like an "Any Internal State" node.

    A little unknown fact about state machine behaviours is that at runtime Unity copies all StateMachineBehaviours that exist on a statemachine node to every AnimatorState inside that statemachine. This script basically bakes nearly any transition you make in the editor from that parent node to the SMB (which unity then copies to every internal node) and checks those conditions on Update to see if it should transition.

    • For some reason NaughtyAttributes is not supported on SMBs and I didn't want to write a drawer. Also for some reason even though SMBs derive from ScriptableObjects they don't seem to raise Awake/OnEnable/OnDisable messages at expected times like they do for normal scriptable object assets. So I resorted to using OnValidate to get the data to bake transitions. This means when you change your transitions you have to manually change anything in its inspector before the transitions copy over.
    • I wasn't able to find a way, in code, to tell an animator to transition to just any node (as in, including AnimatorStateMachine nodes not just AnimatorState nodes) like you can in an animator controller. Thus transitions you define on statemachines that you intend to be copied cannot point to another statemachine directly (the transition simply won't work), its better to have it point to a default state within that statemachine thats tailored to specifically handle these types of transitions
    • Cause it didn't make sense in practice, This smb does not copy transitions that don't have any conditions. Exit times are also not baked (mostly cause that data is not available in UnityEditor.Animations.AnimatorTransition, for some reason)
     
    Last edited: Apr 5, 2023
    kyuskoj, lolium and HamCha87 like this.
  26. lolium

    lolium

    Joined:
    Oct 14, 2014
    Posts:
    33
    worked like a charm! incredible.
     
  27. cmarfil

    cmarfil

    Joined:
    Dec 27, 2016
    Posts:
    6
    Thank you! This works perfectly, you should charge 20 cents per installation my friend :)
     
    LittleSY likes this.