nngafook

Hi!

I have an issue keeping my event handling clean. I'll provide code examples of what I do below. An example of the issue is...
The enemy plays the attack animation, and on animation complete I reset some variables and do other things. But it's possible that it can die mid attack animation. And if timed right, I get the animation complete event fired still. Normally I do checks against the skeletonAnimation.AnimationName to make sure the correct animation is playing when events fire, and only recently did I realize I can/should use the trackEntry passed into the complete event as well (my bad for not knowing). So in this example, I changed the check in the attack complete event callback, to make sure the skeleton's current animation is the attack, which is fine in theory, but the next problem is, I currently add the listener for the event when the animation begins, so if that complete never occurs (cause the skeleton's animation was not attack), I don't remove the event listener. Which makes me have to add a sanity check to the death logic, i.e. if killed mid attack, remove events and reset variables, which I'd rather not do, and let the events kind of be contained within their own callbacks if that makes sense.

I thought I could use the interrupt event, but because that gets called on complete as well, I would have to monitor every interrupt and have a case for any interrupts that occur and what interrupted it... i.e. if attack was interrupted by death and nothing else.

Anyway, I think the more I try to explain, the more confusing it may get. Hopefully the code helps with clarity.

So basically all I'm wondering is how best to manage events and callbacks. Seems like it's probably pretty easy for most, but for some reason I think I'm confusing myself the more I dig into the systems.

Let me know if anything is unclear.

P.S. side question. I used to set animations by skeletonAnimation.AnimationName = attackAnimation; And set the looping separately, but I found the SetAnimation() today... is there any difference between the two, and if not, I assume it's better to use SetAnimation()?
public void Attack() {
skeletonAnimation.state.SetAnimation(0, ATTACK_ANIMATION, false);
skeletonAnimation.state.Complete += OnAttackComplete;
}

// With this version, we [i]do[/i] get into the if check if the enemy dies while attacking
private void OnAttackComplete() {
if (trackEntry.animation.name == ATTACK_ANIMATION) {
// Do things
skeletonAnimation.state.Complete -= OnAttackComplete;
}
}

// With this version, we do [i]not[/i] get into the if check if the enemy dies while attacking
private void OnAttackComplete() {
if (skeletonAnimation.AnimationName == ATTACK_ANIMATION) {
// Do things
skeletonAnimation.state.Complete -= OnAttackComplete;
}
}
nngafook
Posts: 23

Pharan

1. For the most part, at your level, don't use skeletonAnimation.AnimationName. The AnimationName property is a string interface for beginners or fast prototyping.

2. This pattern is sort of tangling up your character controller too much with the animation state. I mean you may have your reasons, but doing this does tend to cause a lot of problems because the state of animation doesn't necessarily have a 1:1 relationship with the state of your character. It's better for the animation to depend on the character state, not the character state to depend on the animation.

That said, assuming you have your reasons for animation-dependent character logic, if you know you don't want the AttackComplete handling to happen anymore (eg, when the character dies, or is interrupted), what programming pattern are you trying to follow that you won't allow yourself to unsubscribe there? What trouble does that cause; or what trouble does only unsubscribing within the handler prevent?
User avatar
Pharan

Pharan
Posts: 4502

nngafook

Hey Pharan.

I mean, honestly nothing is stopping me from unsubscribing at death, or anything else that might happen mid attack. I'm just worried that that's a rabbit hole that might never end, depending on animations and characters. Example, what if enemies can be teleported or something, that's a new thing that can interrupt the attack, and the more things that get introduced, is just more things to have to try to remember, for any enemy that it might happen to basically.

You're right, I think I am tangling myself up, especially with character states sometimes depending on animation and vice versa. In this particular case though, if the attack animation is playing and it dies, I immediately set the character state to dead, but the problem is that the attack complete event still occurs while the dying animation is playing. So variable resets that happen when attack completes assumes that the character is still alive. So again, yeah I can just do a check in the animation complete if the character is still alive, but still feels like a fix, and not the "right" way if that makes sense?

Another question, can you clarify at different points in an animation cycle, when would I want to use the trackEntry.animation.name vs the skeletonAnimation.AnimationName. i.e. when setting an animation and I don't want set it if it's already playing, I know I can check the skeleton, but in complete events, when should I check the skeleton and when should I check the trackEntry. Like in that attack complete callback in my original post, using one gives a different result than the other.

Thanks once again Pharan. As usual, you are my saviour!

-- 08 Sep 2017, 14:36 --

Another quick question on top of all the others :S

Is there any way to have a complete event not fire after it's been "interrupted". And I say interrupted with quotes, because what I really mean is, I call SetAnimation("Attack"), and then before it's done, I can call SetAnimation("Die"), and it plays the death animation, it stays dead (invisible), but I still get the attack complete event. I get that the event fires after every loop, that's fine, but when it's not "playing", is there a way to have it not fire anymore besides manually removing the listener when changing the event.

Thanks!
nngafook
Posts: 23

Pharan

1. As I described, basically, at your level, don't ever use skeletonAnimation.AnimationName.
Apart from the overhead, the rhs AnimationName is equivalent to skeletonAnimation.AnimationState.GetCurrent(0).Animation.Name:
Meaning the name of the animation on the "current" AnimationState TrackEntry on track 0.
That means when an animation starts mixing out, it's no longer current.

If you use the TrackEntry on your event handler, that will always give you the TrackEntry that caused the event to fire.

2. You can check if a TrackEntry is already mixing out by asking:
if (trackEntry.MixTime > 0) {...}
Under normal circumstances (ie, you don't manually modify the value of MixTime), if mixTime is greater than zero, that TrackEntry is already on its way out, and the current animation is something else.

You can set up your attack handler to do nothing if trackEntry.MixTime > 0.
User avatar
Pharan

Pharan
Posts: 4502

nngafook

Awesome! That'll help out a lot!

Thanks Pharan!
nngafook
Posts: 23


Return to Unity