• Unity
  • No events on non looping animation using SetAnimatin

overview:
I'm relying on Complete and Interrupt events to setup/clear some variables after a non looping "attack" animation.

what's happening
After hitting the button in random intervals or holding the button, sometimes the animation gets stuck in the last frame, no events are getting fired and I can't clear the variables on my callbacks. Shouldn't Complete, End or Dispose run on this situation? I mean, even if I keep holding the button, and the animation have ended, aren't they supposed to trigger? User holds attack, character attacks, then goes to idle state (after using the callbacks) etc.

My general code below. I can't generate a working code to test without disclosing atm.


private void OnAnimationComplete(Spine.TrackEntry trackEntry) {
    if (trackEntry.Animation.Name == "attack") {
        onAttackBehavior = false;
        animationBasicAttack = false;
        Debug.Log("attack anim completed");
    }
}

private void OnAnimationInterrupt(Spine.TrackEntry trackEntry) {
    if (trackEntry.Animation.Name == "attack") {
        onAttackBehavior = false;
        animationBasicAttack = false;
        Debug.Log("attack anim interrupted");
    }
}

...

private void SetAnimations() {
    // animations don't run here. It's only to set the state based on user input and character conditions
    // onAttackBehavior and animationBasicAttack are used to define the state here.
    // but they are never released on callbacks above. Then I get stuck on the same state always and playing the same animation (stuck on last frame)
}

private void ApplyAnimations() {
    if (previousState != state) {
        if(state == BodyState.Attack) {
            // this case is running only once, which is fine and desired
            skeleton.SetSkin("whatever");
            skeleton.SetSlotsToSetupPose();
            animationState.Apply(skeleton);
            animationState.SetAnimation(0, "attack", false);
        } else if (...) // bunch of other animation states being applied
    } else {
        // This line keeps running when the attack animation gets stuck in the last frame.
        Debug.Log("keep playing the old animation: " + previousState);
    }

previousState = state;
}
Related Discussions
...

I'm afraid the above code is not sufficient to diagnose your problem. It is missing links where you assign state and from where the methods are called.

I'm not sure what you want to achieve, but depending on your intention, you might want to also consider registering to the End event instead of the Complete and Interrupt events.

Hi Harald! Thanks for your time!
Ok, adding more. isBasicAttacking is coming from user input. Btw, the other evens aren't running as well (End or Dispose)

I solved my problem by checking if the "attack" animation was completed by using the ResolvePreviousAnimations. The code is below.

private void OnAnimationComplete(Spine.TrackEntry trackEntry) {
    if (trackEntry.Animation.Name == "attack") {
        onAttackBehavior = false;
        animationBasicAttack = false;
        Debug.Log("attack anim completed");
    }
}

private void OnAnimationInterrupt(Spine.TrackEntry trackEntry) {
    if (trackEntry.Animation.Name == "attack") {
        onAttackBehavior = false;
        animationBasicAttack = false;
        Debug.Log("attack anim interrupted");
    }
}

...

private void FixedUpdate() { // or Update, it happens either way
    ShouldAttack();
    SetAnimations();
    ApplyAnimations();
}

private void ShouldAttack() {
    if (isBasicAttacking && !onAttackBehavior) {
        Debug.Log("player can attack");
        nextAttack = Time.time + attackRate;
        BasicAttack();
    }
}

public void BasicAttack() {
    Debug.Log("Basic Attack: set onAttackBehavior and animation variables, plus colliders");
    onAttackBehavior = true;
    animationBasicAttack = true;
    //...other stuff
}

// this function is my workaround
private void ResolvePreviousAnimations() {
    if (currentTrackEntry.Animation.Name == "attack" && currentTrackEntry.IsComplete) {
        Debug.Log("currentTrackEntry attack completed. Finish it");
        animationState.ClearTrack(0);
        skeleton.SetBonesToSetupPose();
    }
}

private void SetAnimations() {
    currentTrackEntry = animationState.GetCurrent(0);
    ResolvePreviousAnimations();  // my solution

// ... Other states before
} else if (animationBasicAttack) {
    if (faceDirection.x >= 0) {
        Debug.Log("set state = BodyState.BasicAttack90");
        state = BodyState.BasicAttack90;
    } else {
        state = BodyState.BasicAttack270;
    }
// ... Other states after
}

private void ApplyAnimations() {
    if (previousState != state) {
        if(state == BodyState.Attack90) {
            // this case is running only once, which is fine and desired
            skeleton.SetSkin("whatever");
            skeleton.SetSlotsToSetupPose();
            animationState.Apply(skeleton);
            animationState.SetAnimation(0, "attack", false);
        } else if (...) // bunch of other animation states being applied
    } else {
        // This line keeps running when the attack animation gets stuck in the last frame.
        Debug.Log("keep playing the old animation: " + previousState);
    }

previousState = state;
}

Thanks for getting back to us. Ok, as I can see everything is called from either FixedUpdate or Update consistently.

I see Debug.Log statements but not after the most important line animationState.SetAnimation(0, "attack", false);. Could you add this and check if your animation that you expect to run is at all started? You should then see a SetAnimation debug print line without a following completed or interrupted line.

From your code above I see nothing that would obviously cause problems. Could you perhaps create a minimal reproduction project without the whole state management code? If it cannot be reproduced without the complete state management layer around it, most likely the problem is rooted there.