Search Unity

Question How to ignore animation events on overridden layers ?

Discussion in 'Animation' started by bybiche, Dec 4, 2022.

  1. bybiche

    bybiche

    Joined:
    Nov 15, 2019
    Posts:
    2
    Hi,

    I have two animation layers : a base layer, and a combat layer which overrides the base layer. When I enter combat mode, the combat animations are played, and the base layer animations are ignored, which is the expected behavior.

    However, the events in the base layer are triggered even though the animations are not played ! Is there a way to call an event only if the animation is actually played, and ignore the event if the corresponding animation has been overridden ?

    Thanks for your help,
     
    ExCeLL61 likes this.
  2. Fressbrett

    Fressbrett

    Joined:
    Apr 11, 2018
    Posts:
    97
    I am not sure whether there is a more elegant solution, but what you could try is one of the following approaches:

    • Do a quick check whether you are in combat mode before executing anything else within your Event Handler functions.
    • Don't use events within animations to have full control over what you want to let happen when, and making it easy to switch animations (no need to set-up events again):

    Without events within animations:

    Within the project I am currently working on we use StateMachineBehaviours to dispatch OnStateEnter when and which state was entered. We do this by creating an Enum with all relevant states we want to track, and then select the one we want to set as "active" within the inspector:

    upload_2022-12-9_23-15-55.png

    The StateMachienBehaviour looks like this:
    Code (CSharp):
    1.  
    2. public class PlayerAnimatorStateSetterSMB : StateMachineBehaviour
    3. {
    4.     public AnimatorState AnimatorState;
    5.  
    6.     [Space]
    7.     [SerializeField, ReadOnly]
    8.     private Player _player;
    9.  
    10.     [SerializeField, ReadOnly]
    11.     private PlayerAnimator _playerAnimator;
    12.  
    13.     [SerializeField, ReadOnly]
    14.     private bool _isInitialized = false;
    15.  
    16.  
    17.     private void TryInitialize(GameObject thisGameObject)
    18.     {
    19.         if (_isInitialized)
    20.             return;
    21.         _isInitialized = true;
    22.  
    23.         _player = PlayerManager.GetPlayerByTagComparison(thisGameObject);
    24.         if (_player == null)
    25.             return;
    26.  
    27.         _playerAnimator = _player.PlayerAnimator;
    28.     }
    29.  
    30.     override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    31.     {
    32.         TryInitialize(animator.gameObject);
    33.  
    34.         if (_playerAnimator != null)
    35.         {
    36.             _playerAnimator.SetAnimatorState(AnimatorState);
    37.         }
    38.     }
    39. }

    Basically we grab a reference to a component on the Player through the Animator reference that was passed into the OnStateEnter function. We then call SetAnimatorState() on that component, which tracks and dispatches any changes to the AnimatorState. It also invokes a public System.Action when the State has changed, to which other components can listen to.

    You now know when what state was entered and what state you are currently in within your code.
    What you can do now is do certain things x seconds after a state has been entered.

    I made a "AnimatorStateResponder" component, which holds information exposed to the inspector about what to do when depending on what state was entered. You can also do this in a neatly nested way (here`StateResponse` is just some class that sais what to do):

    Code (CSharp):
    1.  
    2. public Dictionary<AnimatorState, TimedStateResponses> AnimatorStateResponsesMap = new Dictionary<AnimatorState, TimedStateResponses>();
    3.  
    4. public struct TimedStateResponses
    5. {
    6.     public SortedDictionary<float, List<StateResponse>> TimedResponsesMap;
    7. }
     
    bybiche likes this.
  3. bybiche

    bybiche

    Joined:
    Nov 15, 2019
    Posts:
    2
    Thanks for the detailed answer !

    In my project I use the animation event to trigger a footstep sound, which is really convenient to synchronize the sound with the animation.

    Your second solution without events, which is based on a predefined delay "x" after entering a state, is hard to tune in this case (if I accelerate the animation to walk faster, sound and animation will not be synchronized anymore). However I will keep this in mind for events which occur after a fixed delay.

    Your first solution seems the more approriate here. I just use an if-statement in the event, to play the sound only if I am not in combat mode. Strange though that in the "default" behavior, an event can be called even if the animation is not played !
     
    Fressbrett likes this.
  4. Fressbrett

    Fressbrett

    Joined:
    Apr 11, 2018
    Posts:
    97
    I see, yes footsteps are about the only thing where we still use Animation events, for the exact same reason (blending between idle/walk/sprint and animation speed altering). In that case catching the event when in combat should be your best bet for sure :)