abuki

  • il y a 13 jours
  • Inscrit 7 nov. 2016
  • Well, to sum it up. I removed all calls to Update(0) and refactored these places to with better understanding what Spine is doing. So it is not a black box anymore for us. Thanks for helping me with that! Now a lot of things seem obvious, but e.g. playing empty animations for proper mixing is nothing easily understandable.
    And still, I found a runtime bug 🙂

    Next time I will directly try to isolate and understand what is going on instead of quickly hacking it with Update(0) call.

  • Nate Hi, I already ported your libgdx changes to our C# Unity runtime 4.2 as a quick fix. I can see the above problem fixed. Thanks!

  • Nate Great! Any chance of backporting it to 4.2? Or is the beta stable enough to switch to it when in full production?

  • Nate Do you mean the first time the animation is applied the track time is already at 0.14 seconds, so you don't see the first 0.14 seconds of the animation?

    Yes.
    For some reason, it works fine for default mix = 1 or 0. Tried your code (in Unity) and was playing with the following:
    skeletonAnimation.AnimationState.Data.DefaultMix = 0.1f;
    If you set the default mix to anything between 0.01 and 0.99 this mix duration is added to the animation time at the start of the animation. The same happens if you set it to e.g. 1.2, but in that case only 0.2 is added.

    I am seeing this in a basic script I posted above, no special conditions project specific.
    I am also playing it with animation that is non-looping (seems it is not relevant) and is longer than 2s (not sure how long is your walk loop).

  • It works fine when default mix duration on asset is 0

  • Hi again, this is on very similar topic. My default mix duration is set to 0.1 on the asset:

    private void Start()
            {
                skeletonAnimation.AnimationState.ClearTrack(0);
            }
    
            private void Update()
            {
                if (Input.GetKeyDown(KeyCode.K))
                {
                    Debug.Break();
                    skeletonAnimation.AnimationState.SetEmptyAnimation(0, 0);
                    skeletonAnimation.AnimationState.AddAnimation(0, animationReference, false, 0f);
                    // The added animation starts at track time 0.14, how to force it to 0 or 0.04 or anything similar?
                }
            }
  • Nate Looks like you are right, the specific problem was caused by animations on lower tracks starting one frame later than they should. Thanks for ensuring!

  • Doing a huge refactoring and have a question regarding setEmptyAnimation.
    From docs: A mix duration of 0 still mixes out over one frame.
    So what is the best way to mix directly the next frame without calling Update(0)?

    Calling ClearTrack is not what I want, I want to directly mix to setup on a specific track right the next frame. What am I missing now? 🙂

    • Nate a répondu à ça.
    • Hi, thank you very much, I think I am starting to understand what is happening on our side.

      Regarding the docs, I have two points:

      1. https://esotericsoftware.com/spine-api-reference#AnimationState-addAnimation2 - wording of If the track is empty, it is equivalent to calling setAnimation. was misleading to me as I could easily think you mean trackentry which is playing empty animation. Now, I got that you really mean empty track itself (= cleared)
      2. https://esotericsoftware.com/spine-api-reference#AnimationState-setEmptyAnimation might be nice to add link to https://esotericsoftware.com/spine-applying-animations/#Empty-animations as I wasn't aware again of this and your example there (set empty, add after) is probably what I will need to do in my case.

      I will now deep dive into my code and report back when I solve all the issues.

    • Reading it again in the morning, it seems to me that the "correct" behavior is also wrong as the documentation states that If the track is empty, it is equivalent to calling setAnimation. But we can see empty progress to delay, so it looks like actually addAnimation. Maybe there is a difference between track playing empty animation and track which is empty = cleared? That should probably be more differentiated in the docs.

      Note: progressing the empty to delay and then mixing to new animation looks as good preferred behavior to me.

      • Hi,

        I am here again with problems partially related to the SkeletonAnimation.Update(0) call.
        I checked the documentation here https://esotericsoftware.com/spine-api-reference#AnimationState-addAnimation2 and understand that AddAnimation should behave like SetAnimation in this case, but that does not explain following behavior. See the comments in example code:

        private void Update ()
        {
            if (Input.GetKeyDown(KeyCode.K))
                StartCoroutine(Testing());
        }
        
        private IEnumerator Testing()
        {
            skeletonAnimation.AnimationState.SetAnimation(0, animationReference, false);
            yield return new WaitForSeconds(1f);
        
            skeletonAnimation.AnimationState.SetEmptyAnimation(0, 0);
            skeletonAnimation.Update(0); // Observe the change in behavior when there is Update or not
            yield return null;
            yield return null; // For bug to appear it is also required to wait at least 2 frames
                    
            //Debug.Break();
            skeletonAnimation.AnimationState.AddAnimation(0, animationReference, false, 0.2f);
            // If Update(0) is not called, we see empty track progressing up to 0.2s delay, then mixing to new animation - this is correct
            // If Update(0) is called, the new animation is set directly but not progressing time up until 0.2s delay - this is wrong, why?
                    
            // Also, you can change SetEmptyAnimation and Update lines for:
            // skeletonAnimation.AnimationState.ClearTrack(0);
            // Which produce the same behavior
                    
            // You can also try removing the very first call to SetAnimation, then it works bad even without calling Update(0)
            // So there are multiple options how to produce the problem
        }

        So here is my updated code to reflect the proper order of actions:

        // some calculation if animation will be delayed or not
        			
        if (delay > 0)
        	newTrackEntry = SkeletonAnimation.AnimationState.AddAnimation(MainTrackIndex, animationName, isLoop, delay);
        else
        	newTrackEntry = SkeletonAnimation.AnimationState.SetAnimation(MainTrackIndex, animationName, isLoop);
        			
        // Some trackEntry manipulations
        newTrackEntry.MixDuration = 0;
        
        // If delayed (added) subscribe to event, if not (set) call it right now			
        if (delay > 0)
        	newTrackEntry.Start += OnAnimationStarted;
        else
        	OnAnimationStarted(newTrackEntry);

        What I need is to be sure that right at the moment when some animation starts, I need to check if it has some special settings and do something on some other tracks and call Update.

        So this is probably the right solution?

        Hi, Thanks for detailed explanation! Now I got it, I think in that case I will fix it just by removing the Update call.

        Still, there might be (and apparently there was before) a reason to call Update in OnAnimationStart. The Update was called not alone but with using another SetAnimation there. But now I see that would need a different pattern to be sure it is always called after setting trackEntry properties.

        My minor notes to this:

        1. It would be great to have an option to set these trackEntry properties right with the SetAnimation call (before the start event is fired). This order of actions that SetAnimation (and thus OnAnimationStart) is always called before we can manipulate mixDuration was already misleading a few times before, and I need to create some hacks around that. Or am I again missing some option to set it?
        2. I am still not sure what exactly was changed in the runtime that changed my behavior. But I assume that now I understand it more deeply and if there will be again some visual changes, I will dive into it to understand it before doing dirty hacks. Generally, I am just a little concerned about the runtime always providing reliable identical outcome.

        Thanks again for taking time to deep dive into it, I truly appreciate it!

        Harald in general a valid call to immediately reflect certain performend changes within the same frame

        Well, thanks, then I am waiting for your investigation 🙂

        The related question is if calls to Update(0) should ever be needed? I can see multiple calls to this in my code, usually fixing blinking for one frame as some logic changed something and animation is updated one frame later. So calling Update(0) fixes these and everything looks as it should. But maybe this is a bad practice in general and there is some better way?

          Hi Harald,

          Thanks, you are looking into it. The thing now is that I need to understand the change and how it works and if I should update my code or wait if you will be making some changes.
          Because I can see that I can probably remove the Update call, which definitely was there for some reason, but I can't find any problems when removing it, so maybe something else was fixed in between?

          So I don't need a quick fix, I better need to understand the issue properly and what is a general good practice.

            Here is code for an isolated problem, see the last lines with TODO and comments. I would like to hear your thoughts on this?

            using Spine;
            using Spine.Unity;
            using UnityEngine;
            
            public class AnimationTest : MonoBehaviour
            {
                private const int MainTrack = 0;
                private const int HeadTrack = 1;
            
                [SerializeField] private SkeletonAnimation skeletonAnimation;
                [SerializeField] private AnimationReferenceAsset idleAnimation; // idle with keyed bone
                [SerializeField] private AnimationReferenceAsset walkAnimation; // walk without keyed bone
                [SerializeField] private AnimationReferenceAsset headAnimation; // head animation without keyed bone
            
                private void Start()
                {
                    // Start idle
                    skeletonAnimation.AnimationState.SetAnimation(MainTrack, idleAnimation, true);
            
                    // Watch animations start
                    skeletonAnimation.AnimationState.Start += AnimationStateOnStart;
                }
            
                private void Update()
                {
                    if (Input.GetKeyDown(KeyCode.T))
                    {
                        // Start walking
                        // AnimationStateOnStart will be fire and head animation set
                        var mainTrackEntry = skeletonAnimation.AnimationState.SetAnimation(MainTrack, walkAnimation, true);
                        mainTrackEntry.MixDuration = 0;
                    }
            
                    if (Input.GetKeyDown(KeyCode.U))
                    {
                        // Back to idle
                        skeletonAnimation.AnimationState.SetAnimation(MainTrack, idleAnimation, true);
                        skeletonAnimation.AnimationState.SetEmptyAnimation(HeadTrack, 0);
                    }
                }
            
                private void AnimationStateOnStart(TrackEntry trackEntry)
                {
                    // Checking if animation that needs independent head (like walk) started
                    if (trackEntry.TrackIndex == MainTrack && trackEntry.Animation == walkAnimation.Animation)
                    {
                        var headTrackEntry = skeletonAnimation.AnimationState.SetAnimation(HeadTrack, headAnimation, true);
                        headTrackEntry.MixDuration = 0;
            
                        skeletonAnimation.Update(0); // TODO: This is causing the problem!
                        // When Update(0) is called, the bone stays in idleAnimation position where it was interrupted
                        // When Update(0) is not called, the bone mix to setup position as intended
                        // This behavior changed after spine runtime update
                        // Update(0) is here to prevent missing heads for one frame in some specific cases, but not sure if should be needed?
                    }
                }
            }

              Ok, when trying to isolate the problem it looks like that in very basic scenario it works like intended. Now I need to dive deeper to see what is specific to my setup.

              Harald If this is not your result, you might have discovered a bug. Could you please describe what result you get?

              After the runtime update, I see the key from mixed out animation is preserved in the following example, no to the setup pose. It stays in the the position where mixing started. Now the question is if it is related purely to runtime or something in my code. I will try to investigate more.
              Do you have any test examples pre-ready for these?

              Harald When selecting an animation in the Tree view, you have options Export and Layered.

              Yeah, I found it, just wanted to read some more detailed info how exactly the Layeredworks.

                Thanks for explaining.

                It rises some questions, but if there is a detailed documentation for this, please give me the link, but I am unsure if I was able to find it.

                To understand it properly, let's have the following example:
                We have a bone A, animations1-3 and 3 tracks.
                Track 0 - empty or unset (in our case, several empty tracks waiting for specific animations)
                Track 1 - animation1 that animates bone A (has keys for A) which is interrupted in the middle, mixed to other animation2 which has no keys for A
                Track 2 - another third animation3 that has no keys for A

                What is the correct and expected behavior? After the mixing, should bone A have keys from setup or from interrupted animation1 (depending on when we started mixing)?

                And please note that from my observation, this behavior changed with the previous code change in the first post. And that what I see in Spine Preview (trying to simulate something very similar) is different from the Unity runtime. There is still a small chance that something is bad in my code, but as I said it was working ok before runtime update. Also I need to be sure what is the correct behavior before diving deeper.

                Another edge case might be what happens when on track 1 or 2 happens mixing during mixing, or calling update or anything special like that (there are some cases like this, difficult to simulate in Spine editor preview). May I ask if you have any test projects for testing these layering cases? Maybe I can try it on them?

                Didn't know about the Layered tick box! That sounds very useful, can you please share a more detailed info or docs link on it?