- Home /
Mecanim - Trigger event at end of animation state
Not sure if I'm doing something wrong here or totally lost in the way the system works. Here is the situation:
I have an actor on the floor and handcuffed, his animation state is called handcuffedIdleState. When the other actor beats him up he will go to a new state called handcuffedKickedInHeadState by using a trigger:
animController.anim.SetTrigger("isKickedInHead");
Once that animation has played, he returns to the original state handcuffedIdleState. I need to know when the actor has finished the handcuffedKickedInHeadState so I can trigger his reaction. To do that I run the following function on Update():
if(animController.currentBaseState.nameHash == animController.handcuffedIdleState){
//React to kick
}
Now my problem is, since he STARTED on handcuffedIdleState, this will immediately be true, even before the animation started. So I decided to add a couple more lines.
//Make sure the 'isKickedInHead' trigger is no longer true AND
//That the animation is not in transition
if(
!animController.anim.GetBool("isKickedInHead") &&
!animController.anim.IsInTransition(0) &&
animController.currentBaseState.nameHash == animController.handcuffedIdleState
){
//React to kick
}
But still, this is immediately true even before the handcuffedKickedInHeadState started. I can't figure out a sure way of knowing when the new animation has finished and I've arrived at the Idle State...
UPDATE - Another way to look at the problem
So the main issue here is that I'm returning to the animation I started. I'm going from A(1) -> B -> A(2) and I'm trying to figure out when I'm back at A.
B can be anything so its not something I can rely on. B can be a different state so putting events on B or even trying to track it is not an option.
This is what I usually do:
if(
!animController.anim.IsInTransition(0) &&
animController.currentBaseState.nameHash == animController.stateA
){
The problem is, once the transition is over, for ONE frame, before B becomes the currentBaseState, A(1) is the currentBaseState. This makes it impossible to use A for the comparison...
In case I'm not being clear, here is a little graphic I did for myself to understand the problem (the orange box is the problematic frame):
Answer by castor · Jul 18, 2014 at 06:03 PM
I asked the question on the forums and TonyLi gave me the solution. Posting here for future reference.
The way to solve my issue is by knowing what is the next state that is getting played during the transition using GetNextAnimatorStateInfo .
So by doing something like this:
if(
anim.IsInTransition(0) &&
anim.GetNextAnimatorStateInfo(0).nameHash == animController.stateA
){
//Do reaction
}
This makes sure that I'm only triggering the reaction when I'm in the transition OUT of stateB and towards A (since i would never be transitioning from A to A). So this works cleanly and super nice no matter what the initial state is!
For more details on the question, here is the forum link http://forum.unity3d.com/threads/mecanim-going-from-statea-stateb-statea-knowing-when-second-statea-has-started.257635/
Answer by Xtro · Jul 17, 2014 at 05:59 PM
You can add a event at the end of the mecanim animation so that Unity will call your event method when it hits the event trigger on the animation automatically.
For more info, please see the answer under this question : http://answers.unity3d.com/questions/615041/mecanim-animation-events-1.html
But then what is the point of using the state machine? The kickInHead can trigger different animations depending on the situation. It doesn't make sense to me that I need to create events at the end of every possible state to know that I left it. Isn't that the purpose of using $$anonymous$$ecanim? (Also I was the one who replied to that answer :P)
I think he means put the trigger @ the end of the "is$$anonymous$$ickedInHead" animation.
If there shouldn't always be a reaction at the end of that, just add a 2nd boolean or counter in your scripting so that although the function is called, if no reaction is needed none is started.
Thats what I understood. But the "is$$anonymous$$ickedInHead" is just a trigger, it calls different "kicked in head" states with different animations depending on the situation. I also don't like the idea of having events on the animations (unless its for audio of pfx) since sometimes the animation might be used in other states and its not something you can clearly see in your script or the state machine, you have to go and dig on your animation list.
@Xtro Unfortunately Animation events are very unreliable. Here are problems I encountered: http://forum.unity3d.com/threads/animation-events-not-triggered.341916/
Not only animation events. I've realized that the solution I've presented doesn't work well when time is speed up, where this unique frame moment is easily skipped.
Yes, as stated in http://answers.unity3d.com/questions/806949/animation-events-not-firing.html the frame can be skipped and event not fired. When you need event at start or end of animation you can write State$$anonymous$$achineBehavior script and add it on animator state (not animation!)
Answer by DarkGate · Jan 31, 2016 at 02:28 PM
This is a bit late, but you can use a coroutine to check for the end state. In your case, it would be:
IEnumerator onComplete() {
while (animator.GetBool("kickedInHead")) {
Debug.Log("Animation in progress!");
yield return null;
}
// this will get here when kickedInHead is false
Debug.Log("Animation complete!");
}
Answer by Alverik · Apr 30, 2016 at 11:35 AM
ummm... still using mecanim animation events, couldn't you just have used two copies of the same idle? Then used events in the first, transitioned to the hit, then transitioned to the other copy of the idle (the one without the events)...
Although, I guess, project space wise, it may not have been the most efficient method (if you have a small target size)...