SoulGame

Hi there,

I guess this question would be for @Pharan. Thanks for this new spine-unity runtime, the well documented code is so cool :happy: I've managed to solve most issues due to the update except one that bugs me.

Context:
  • I have an idle character who plays Attack animation when I press a key.
  • Pressing the key repeatedly will chain two different Attack animations fluently.
  • When skeletonAnimation.State.Complete happens, the character goes back to Idle

That worked perfectly with previous runtimes (from last month), like shown here:


But now, it glitches if I press the key again at the very end of the previous Attack animation. I think it's precisely while it's mixing back to idle. The character doesn't not Attack at all, instead, it makes a weird glitch/stutter like shown here:


Can you think about changes made to the way event system are triggered? Or maybe how mixing is handled? It seems like the runtime if forcing the mixing back to idle, bypassing the new attack animation, but still kinda play it, it's weird!
User avatar
SoulGame
Posts: 86

Pharan

Hey!
AnimationState in spine-csharp handles mixing. It should handle all cases now, but yours might have something super special that we didn't account for.

If you could send your skeleton export and tell us which animations to play, we can check and fix the code where we can.
unity@esotericsoftware.com
User avatar
Pharan

Pharan
Posts: 4427

SoulGame

I think it's most likely not some super complicated case, it's probably just me missing something as usual.
To clarify the debug, I set all transition time to 0, and used only one attack animation (it glitches the same way, with one or two attack anim).
A = Idle
B = Attack


Glitch is happening like that:
  • B is about to end
  • I ask to play B again
  • B starts playing again but, somehow the event Complete is still sent which makes the character go back to A and prevent B.

I don't understand why this happen with the new Runtime. I do unsubscribe the previous Complete callback when a new animation is asked so it shouldn't happen anyway.

-- 01 Août 2017, 08:13 --

Oh Jeez, I have other issues coming up of canceled animations, that seem to also be due to weird animation Complete behavior.

Here the code for playing animation, can you see anything wrong?
public void Play (string id, bool loop = false, Action completeCallback = null, float timeScale = 1, bool forceRepeat = false, float startAtProgress = 0) {
// Check if not repeating (or skip that check with forceRepeat)
if (CurrAnimID != id || forceRepeat) {

// Unsuscribe events
skelAnim.state.Event -= handleUserEvents;
if (CompleteCallback != null) {
skelAnim.state.Complete -= handleComplete;
CompleteCallback = null;
}

// Play animation
Track = skelAnim.state.SetAnimation(0, id, loop);
SetTimeScale(timeScale);
CurrAnimID = id;

if (startAtProgress != 0) {
GoToProgress(startAtProgress);
}

// Suscribe events
skelAnim.state.Event += handleUserEvents;
if (completeCallback != null) {
CompleteCallback = completeCallback;
skelAnim.state.Complete += handleComplete;
}
}
}

private void handleComplete (TrackEntry track) {
if (CompleteCallback != null) {
CompleteCallback();
skelAnim.state.Complete -= handleComplete;
}
}


-- 01 Août 2017, 09:09 --

After even more research => it is indeed an issue of undesirable OnComplete events which are called even after I started another animation.

NORMAL CASE
  • Idle is played
  • Attack is played.
  • There is a listener OnComplete on AnimationState so when Attack ends, we go back to Idle
  • Attack finishes and we go back to Idle as planned. Everything is in the right place :yes:

BUG CASE
  • Idle is played
  • Attack is played but this time near the end of Idle
  • In that case, the OnComplete listener which was supposed to be triggered by Attack ending, is almost instantly triggered by what I suspect to be Idle ending clandestinely... :tmi:
  • That's not normal. Playing Attack should kill Idle animation and prevent it to trigger undesirable OnComplete event, right?

NB: your last message is the n°4242, that should be enough to get the ultimate answer :giggle:
User avatar
SoulGame
Posts: 86

Nate

You should check what animation completed when you get the onComplete event. However, it may be easier to queue playing idle after attack when you play attack:
state.setAnimation(0, "attack", false);
state.addAnimation(0, "idle", true, 0);
User avatar
Nate

Nate
Posts: 7520

SoulGame

Checking what animation just completed seems like a dirty fix because logically, the idle animation shouldn't ever send a OnComplete event, heck, it didn't send one in the previous runtime! Unless it all comes from an unexpected interaction between my code and the changes made to the new runtime.

I don't know if queuing animation would be convenient because player is supposed to interrupt action whenever he wants. Unless I can break queue? I'll check that.
User avatar
SoulGame
Posts: 86

badlogic

Sending completion events makes sense, as it indicates that one loop of a looped animation has completed. This can be used to synchronize other events.
User avatar
badlogic

Mario
Posts: 1011

SoulGame

Even when the animation has been cut by another..?
User avatar
SoulGame
Posts: 86

Pharan

I instinctively recommend that a game-logical system should be controlling what animations should be played, rather than animations relying on other animations to know what next to play. (ie, the "complete" event should be on the game logic side and not the animation side)
It does sound like AnimationState is doing what it's supposed to be doing: Whenever an animation reaches the end of its duration, even if it's fading out, Complete will fire.

There might be something else keeping you from doing this, so I don't know.

So if the current behavior of AnimationState isn't desirable, we can remove the code that fires events for tracks that are fading out, or create a new event type that's Complete but only for tracks that are currently playing (not fading out). This will entail having to make that edit to AnimationState every time you update spine-unity/spine-csharp.

Though I suppose this might be a common case. The question is, when is Complete when fading out desired or not desired, not just for your game but for most people's?

Note that this has only become a visual-artifact problem because the new AnimationState handles mixing unkeyed items back to setup pose so they look like they do in Spine editor. Most people benefit from this as it results in a more reasonable workflow for the animator and the programmer.
User avatar
Pharan

Pharan
Posts: 4427

Nate

It makes sense to fire the complete event even for animations which are being mixed out. It is trivial to check if the track entry for the complete event is the current entry for that track.
SoulGame wrote: the idle animation shouldn't ever send a OnComplete event
The complete event happens every time an animation has reached the end. For looping animations (like your idle, I assume) it will occur many times.
SoulGame wrote:I don't know if queuing animation would be convenient because player is supposed to interrupt action whenever he wants. Unless I can break queue?
If you call setAnimation, everything in the queue will be mixed out and replaced by the new animation(s).
User avatar
Nate

Nate
Posts: 7520

SoulGame

Thanks for your answers.

@Pharan:
I understand your instinct, and yeah generally speaking I tend to rely too much on animation for my gameplay logic. That being said, if I want my character to go back to idle after attack, it seems okay to rely on attack's OnComplete event to do that job correctly (and once again, until now, it always worked out...). Actually, I don't even know how I should do otherwise :think: Except with an arbitrary timer..? Meh.
It does sound like AnimationState is doing what it's supposed to be doing: Whenever an animation reaches the end of its duration, even if it's fading out, Complete will fire. [...]
The question is, when is Complete when fading out desired or not desired, not just for your game but for most people's?
I'd gladly share my opinion on that: If I play an animation, I'm not expecting the previous one to send an OnComplete event in the background just because it was reaching its end, the fact that it currently does honestly defy all logic for me. Although I might not see the whole picture, it just instinctively feels wrong. By playing a new animation, I clearly state, as a developer, that I want to change the animation, so the next time OnComplete is called, it should be about THAT animation, not the previous one. So yeah, unless I'm missing something, I'd definitely suggest to consider that an animation that has been interrupted by another should not be able to send an OnComplete event.

I'm really not a fan to make change to the plugin as I think it's always more clever to respect and adopt the philosophy of the people developing it. You guys work hard on this, so if you don't agree with me, you probably have your reasons, and I shall try to work with your logic.

@Nate:
The complete event happens every time an animation has reached the end. For looping animations (like your idle, I assume) it will occur many times.
Yup, I'm aware of the event behavior, the doc is cristal clear, what I meant was: for me, the idle animation shouldn't ever send an OnComplete event after it has been canceled/replaced.
It is trivial to check if the track entry for the complete event is the current entry for that track.
I'm not sure I agree with that for my situation, but it might be because I've not figured out the whole thing yet :tmi:
Thanks for the heads up with queuing, I'll dig into that more, maybe I should rethink my whole transition logic using those.
User avatar
SoulGame
Posts: 86

Nate

SoulGame wrote:unless I'm missing something, I'd definitely suggest to consider that an animation that has been interrupted by another should not be able to send an OnComplete event.
This would limit someone else who does want the complete event to occur for animations mixing out. Maybe they are turning on/off particles or something and that should occur even when mixing out.

I think the real problem is that you've subscribed to all AnimationState events (AnimationState addListener). You could instead subscribe to the attack TrackEntry events (TrackEntry listener) and then you won't need to filter events your listener doesn't care about.
User avatar
Nate

Nate
Posts: 7520

SoulGame

@Nate
This would limit someone else who does want the complete event to occur for animations mixing out. Maybe they are turning on/off particles or something and that should occur even when mixing out.
Yep, so to go through with my logic, it would require to add an event OnCut or OnInterrupted (isn't there one already?) which would allow user to properly set his/her particles and stuff. From a logic/semantic perspective, that would be clearer than sending an OnComplete event for an animation that actually didn't complete.

Thanks for the lead you gave on track vs state listener, I'm gonna check that out as well. Although I'm not sure it's an issue because I use only one track.

-- 02 Août 2017, 07:58 --

@Pharan
So if the current behavior of AnimationState isn't desirable, we can remove the code that fires events for tracks that are fading out, or create a new event type that's Complete but only for tracks that are currently playing (not fading out).
Even though I want to fix this properly in the long run, for the time being, I'd be grateful if we could do that. Maybe the simplest way would be to change when OnComplete is send as you suggested? Could you give me a hint on how to do that? Because I've found the event and the Drain method but not where you actually say to send it.
User avatar
SoulGame
Posts: 86

Pharan

Actually come to think of it, if you know you're interrupting the animation, can't you unsubscribe from the Complete event right there?
That would save you from having to edit AnimationState.

This also works best, as Nate described, when you subscribe to the TrackEntry Complete event rather than the AnimationState Complete event.

Subscribing to the TrackEntry Complete event
var trackEntry = AnimationState.SetAnimation(0, "something", false);
trackEntry.Complete += HandleComplete; // handle the Complete event of this animation that you just played.
Unsubscribing to the old event.
trackEntry.Complete -= HandleComplete; // track was interrupted. Ignore its complete event.
User avatar
Pharan

Pharan
Posts: 4427

Nate

SoulGame wrote:Thanks for the lead you gave on track vs state listener, I'm gonna check that out as well. Although I'm not sure it's an issue because I use only one track.
Note it's "track entry vs animation state listener". You use one track, but many track entries.
User avatar
Nate

Nate
Posts: 7520

SoulGame

@Nate: Thanks for that additional note, that's very helpful, I didn't realize the difference. Being able to focus on a single entry should indeed help a lot.
So far I managed to solve most cases using the queue system you suggested. For the remaining cases where I need accurate OnComplete, I'll check if that works with track entry listener.

@Pharan: good idea! I'll probably use that.

Hopefully I should be able to fix all the issues with the solutions you gave me :whew:

Thanks! :yes:
User avatar
SoulGame
Posts: 86


Return to Unity