Search Unity

Mecanim trigger stays down (queued?)

Discussion in 'Animation' started by Noktai, Mar 29, 2015.

  1. Noktai

    Noktai

    Joined:
    Feb 11, 2013
    Posts:
    40
    Hello. I've been fighting around this problem for a long time, but now that unity5 is out I was hoping there was an option to change this.

    Whenever I call setTrigger(), and the state-machine is not in a state where it would cause a transition, it causes the trigger to stay true.

    The red square represents the input to start the drill state.


    Whenever I'm in the idle state, the transition works fine. But when running or jumping and calling setTrigger("tryDig") just once, it stays locked until I enter the idle state. Which then goes into the digging state because there's a transition from idle->digging when the trigger is true.

    It seems like the trigger is queued, which is not something I want. I could fix this by using booleans, but it feels incredibly ugly to enable and immediatly disable a boolean just to trigger a transition.

    Thanks!
     
  2. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    theANMATOR2b likes this.
  3. Noktai

    Noktai

    Joined:
    Feb 11, 2013
    Posts:
    40
    I should've taken a better look at the docs, thanks for the response!
     
  4. edwon

    edwon

    Joined:
    Apr 24, 2011
    Posts:
    266
    Is there a way to change this behaviour? I want to have triggers that are guaranteed to only be true for one frame. I just spent two weeks designing a system around that assumption.
     
  5. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,336
    It's pretty easy to make the extension method. Assuming you have some sort of static coroutine system:

    Code (csharp):
    1. //usage:
    2. myAnimator.SetTriggerOneFrame("Trigger");
    3.  
    4. //extension methods to allow that usage:
    5. public static class AnimatorExtension {
    6.  
    7.     public static void SetTriggerOneFrame(this Animator anim, string trigger) {
    8.         StaticCoroutine.Start(TriggerOneFrame(anim, trigger));
    9.     }
    10.  
    11.     private static IEnumerator TriggerOneFrame(Animator anim, string trigger) {
    12.         anim.SetTrigger(trigger);
    13.         yield return null;
    14.         if (anim != null) {
    15.             anim.ResetTrigger(trigger);
    16.         }
    17.     }
    18. }
    If you don't have a static coroutine system, you can send the MonoBehaviour as a parameter to the method, and run the coroutine on that:

    Code (csharp):
    1. //usage:
    2. myAnimator.SetTriggerOneFrame("Trigger", this);
    3.  
    4. //extension methods to allow that usage:
    5. public static class AnimatorExtension {
    6.  
    7.     public static void SetTriggerOneFrame(this Animator anim, MonoBehaviour coroutineRunner, string trigger) {
    8.         coroutineRunner.StartCoroutine(TriggerOneFrame(anim, trigger));
    9.     }
    10.  
    11.     private static IEnumerator TriggerOneFrame(Animator anim, string trigger) {
    12.         anim.SetTrigger(trigger);
    13.         yield return null;
    14.         if (anim != null) {
    15.             anim.ResetTrigger(trigger);
    16.         }
    17.     }
    18. }
    19.  
    Since the animator API is so sparse, extension methods can help make it a lot more readable. Here's an example:

    Code (csharp):
    1. //what you want:
    2. if(myAnimator.IsInState("SomeState"))
    3.  
    4. //The horribly cumbersome API you're stuck with:
    5. if(myAnimator.GetCurrentAnimatorStateInfo(0).IsName("SomeState"))
    6.  
    7. //extension method that allows the first version:
    8. public static bool IsInState(this Animator animator, string stateName, int layer = 0) {
    9.     return animator.GetCurrentAnimatorStateInfo(layer).IsName(stateName);
    10. }
    11.  
     
  6. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    Just be aware that GetCurrentAnimatorStateInfo is not a free call, if you only need to check the name that ok but if you need to check a few setting and each time you call GetCurrentAnimatorStateInfo you may get a performance hit
     
  7. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,336
    Checking what state the animator is in is really important for a lot of applications, and it should be possible to do it for (almost) free. Implementing that would be really easy - just store the hash of the current state on the animator, and expose an IsInState method that checks against that hash.

    The #1 question people ask about with regards to the animator is "how do I check which animation is playing". Since neither OnEnter nor OnExit gives accurate information about what state you're in (and require a lot of boilerplate work), you should really just create Animator.IsInState.
     
  8. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    I do not agree, on each statemachine behaviour callback you get the AnimatorStateInfo for the current state
    http://docs.unity3d.com/ScriptReference/StateMachineBehaviour.OnStateEnter.html
    http://docs.unity3d.com/ScriptReference/StateMachineBehaviour.OnStateExit.html

    Why this information is not reliable?
     
    ben-rasooli likes this.
  9. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,336
    A bit poor wording on my side. They give accurate information about "some state was entered", but can't give information about what state the system is in.

    Say you need to check what state the animator is in, and need it to be fast, so you can't use GetCurrentAnimatorStateInfo. It's possible to work around it by putting something like this on all of the states:

    Code (csharp):
    1. public class ReportState : StateMachineBehaviour {
    2.  
    3.     [SerializeField] private string name;
    4.     private SomeBehaviour behaviour; //Some behaviour that needs to know the current state
    5.  
    6.     public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    7.         if (behaviour == null) {
    8.             behaviour = animator.GetComponent<SomeBehaviour>();
    9.         }
    10.         behaviour.ReportAnimatorInState(name);
    11.     }
    12. }
    That works, for sure. It's unreliable because you have to remember to add it to every new state. So if you add a new state, and forget to add the behaviour, the query "what state am I in" will stop working.


    The point I was trying to make is that the StateMachineBehaviour system does not give a good way to check "what state is the animator in".
     
    ben-rasooli likes this.
  10. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    yes i agree, using a SMB to do this imay not be the right tool.

    This is not true, you can add a SMB on a statemachine and all state and substatemachine in this statemachine will inherit from this SMB. The important twist is to add the attribut [SharedBetweenAnimators] on this behaviour to avoid having one instance per state, take a look at this package, the controller have a layer with a 3 level deep statemachine, on the top most statemachine there is one SMB that does all the work
     

    Attached Files:

    mr_duke likes this.
  11. eobet

    eobet

    Joined:
    May 2, 2014
    Posts:
    176
    I also fell into this "trap":



    For each input, I set some trigger, which even if it is "blocked" by a currently playing animation with no exit time, or in a state which doesn't care about that particular trigger, gets stored and triggers itself when it feels it's its turn.

    If I want the trigger to immediately expire if it's not able to trigger in the current "update", is the above frame example still the way to do?

    I guess I perhaps shouldn't use triggers, but booleans, and only play an animation state when that particular boolean is true and the immediately exit the state once it turns false...

    Weird that this is the only thread on this topic, so apparently it gives all the answers. I just have to figure them out, I guess. :)
     
    Last edited: Jun 11, 2018
    MassimoFrancesco likes this.
  12. eco_bach

    eco_bach

    Joined:
    Jul 8, 2013
    Posts:
    1,601
    Agreed. This is confusing for anyone not extremely experienced with Mecanim.
    I have used only Triggers and they bare causing me headaches in that the seem to be called twice.
     
  13. ETGgames

    ETGgames

    Joined:
    Jul 10, 2015
    Posts:
    101
    If setting the trigger in some kind of update, i'd just reset the trigger at the start of that update function