• RuntimesUnity
  • 切换动画时SkeletonAnimation回到了SetUp的默认状态一帧,然后才会恢复正常

如标题所示

切换动画就是使用了SetAnimation这个方法,在同一轨道上进行切换。

此外需要注意的时,我是通过配置的时间轴来切换动画的,因为此前有切换后抖动的问题,所以判断是否需要刷新动画时,我提前了一帧(0.02s)通过判断,并将TrackEntry的TrackTime设置为一个合适的负值。

期待大师的解答,谢谢

Related Discussions
...

在切换动画时,SkeletonAnimation回到SetUp的默认状态一帧,然后才会恢复正常,这通常是由于动画混合(crossfading)或动画状态更新顺序的问题。

根据你提供的信息,你使用了 SetAnimation 方法在同一轨道上切换动画,并且通过配置的时间轴来切换动画。为了避免抖动问题,你提前了一帧(0.02s)进行判断,并将 TrackEntryTrackTime 设置为一个合适的负值。

以下是一些可能的解决方案:

  1. 设置混合时间为零
    你可以通过将 MixDuration 设置为零来避免动画之间的混合,从而防止回到默认状态一帧的问题。

       TrackEntry entry = skeletonAnimation.AnimationState.SetAnimation(0, anim, false);
       entry.MixDuration = 0;
       skeletonAnimation.Update(0);
  2. 使用 SetEmptyAnimation
    在切换动画之前,可以先使用 SetEmptyAnimation 方法来清空当前动画,这样可以确保动画切换时不会出现回到默认状态的问题。

       skeletonAnimation.AnimationState.SetEmptyAnimation(0, 0);
       skeletonAnimation.AnimationState.SetAnimation(0, anim, false);
  3. 更新骨骼的世界变换
    在切换动画后,调用 skeleton.UpdateWorldTransform() 方法来确保骨骼的变换立即生效。

       skeletonAnimation.AnimationState.SetAnimation(0, anim, false);
       skeletonAnimation.skeleton.UpdateWorldTransform();
  4. 避免在每帧调用 SetAnimation
    确保 SetAnimation 方法只在需要切换动画时调用,而不是在每一帧中调用。

       if (shouldSwitchAnimation) {
           skeletonAnimation.AnimationState.SetAnimation(0, anim, false);
       }

通过以上方法,你可以避免在切换动画时SkeletonAnimation回到SetUp的默认状态一帧的问题。如果问题依然存在,请检查你的动画配置和时间轴设置,确保它们与上述方法兼容。

    20240626-171102.bin
    39kB
    20240626-171107.bin
    39kB
    20240626-171109.bin
    39kB

    以上三张图片为:
    1.错误帧(切换动画的那一帧) 前的一帧 动画A
    2.错误帧 动画B
    3.错误帧(切换动画的那一帧) 后的一帧 动画B

    Spinebot 上面的方法都尝试了,但仍不能解决问题

    @Bali_Jun Unfortunately machine translation was not very clear, so i don't completely understand your current setup.

    Also, unforutnately the screenshots seem to have been uploading in the wrong format, I see three .bin files, while I assume that you intended to upload .png or .jpeg or similar image files here.

    此外需要注意的时,我是通过配置的时间轴来切换动画的

    As some part is translated as "timeline" for me, are you saying that you are using the spine-unity Timeline Extensions UPM package? Or what exactly do you mean by the above sentence?
    Could you please share some code of what you are doing? And please also share some screenshots which show your setup (especially if using the Timeline UPM package, please share your timeline animation setup).

      Harald
      切换动画的主要逻辑和简单

      if (.....)
      {
        TrackEntry trackEntry = kvp.Value.AnimationState.GetCurrent(0);
         trackEntry = kvp.Value.AnimationState.SetAnimation(0, spineAnimNameDic[animData.spineNum], true);
          float timeScale = 1000f / (nextNodeData.startTime - curNodeData.startTime) * animData.loopTimes;
          trackEntry.TimeScale = timeScale;
          trackEntry.TrackTime = (nowMTime - curNodeData.startTime) / 1000f * timeScale;
      }

      以上内容我在每 fiexedFrame检查,并在合适的时候调用
      TrackTime设置时是一个很小的负值,但错误的表现应该与此无关
      此外MixDuration 全部设置为了0

      至于我说的 "Spine动画突然回到了SetUp默认状态一帧“,所谓的SetUp默认状态就是 Skeleton Data Asset 文件在预览时,从Preview窗口看到的默认状态,也可以通过点击上方的Setup Pose来回到此预览状态

        Bali_Jun
        另外,美术各个动画的首尾帧也都K为了关键帧,我也尝试了使用 skeletonAnimation.Update
        skeleton.UpdateWorldTransform 等方法,但都没有效果,仍然会出现错误的,回到SetUpPose状态的一帧
        您的经验比较丰富,请问有其他导致此状态的可能吗

          Bali_Jun 我发现这个错误的显示和SetAnimation的参数 loop 有关,因为有时目标动画只需要循环播放1次,而有时需要循环播放多次,所以我默认都设置循环状态loop为true了,这本应没什么问题,毕竟只播放一次的在播放结束时就会被及时SetAnimation为其他动画

          可事实上,当我把这个loop参数改为false时,就不会再出现回到SetUp默认状态的问题了

          Harald 这个问题我虽然不知道触发原因,但已经找到解决方法了...
          那就是不要尝试在SetAnimation中 设置循环loop为true,若想循环播放,就通过自己建立的字典与Complete回调来多次调用 SetAnimation(loop = false) 来实现
          虽说仍然不知道触发原因,但解决了问题就好

          如果您后续知道了触发问题的原因,仍然可以回答我,在这里还是谢谢您了

          Harald 除此之外,我想询问的是:假如说我希望在11.21s这个时间点开始播放动画A,但实际上最接近的一帧为11.22s,那么此时我是否需要微调 TrackEntry.TrackTime来达到一个更准确的表现效果?

            Harald 还有一个麻烦的问题..
            假如我使用AddAnimation 添加了四个动画到队列中,那么只有当最后一个动画播放完毕时才会调用 OnComplete回调,这似乎和我在文档上看到的不一样,请问这是正常的吗

              Thanks for the additional information.

              Bali_Jun TrackTime设置时是一个很小的负值,但错误的表现应该与此无关

              Why would a negative track time not be a problem?
              You should limit the TrackTime to 0 via trackTime = Mathf.Max(0f, trackTime) then.
              Have you tried limiting the trackTime like this?

               TrackEntry trackEntry = kvp.Value.AnimationState.GetCurrent(0); // <-- this line has no effect
                 trackEntry = kvp.Value.AnimationState.SetAnimation(0, spineAnimNameDic[animData.spineNum], true);

              Here the first line has no effect. I assume that your original code does more than the code shown. What else happens in the original code?
              In general I see nothing obviously wrong with your code, except if you set negative track times.

              Bali_Jun 此外MixDuration 全部设置为了0

              This will not solve any issues of showing the setup pose for a single frame.

              Bali_Jun 另外,美术各个动画的首尾帧也都K为了关键帧,我也尝试了使用 skeletonAnimation.Update

              Which skeletonAnimation.Update? There are two method, one which accepts the deltaTime

              Bali_Jun 除此之外,我想询问的是:假如说我希望在11.21s这个时间点开始播放动画A,但实际上最接近的一帧为11.22s,那么此时我是否需要微调 TrackEntry.TrackTime来达到一个更准确的表现效果?

              I'm not sure what you would like to achieve. Skeleton animation in general is continuous and has no clear stepped frames, it's just seconds of playback time. If you want to precisely place something on a certain frame with 60fps, just set the TrackTime to frame / 60.0f, or frame / 30.0f for 30fps.

              Bali_Jun 假如我使用AddAnimation 添加了四个动画到队列中,那么只有当最后一个动画播放完毕时才会调用 OnComplete回调,这似乎和我在文档上看到的不一样,请问这是正常的吗

              No, OnComplete is called every time an animation completes a loop:
              http://esotericsoftware.com/spine-api-reference#AnimationStateListener-complete

              In general it is never necessary to avoid setting loop to false and add animations multiple times to emulate the same behaviour. If you need this, something is wrong with your logic code. If you can create a minimal Unity project with minimal code that still shows your issues, we can have a look at what's going wrong. You can send the Unity project as zip package to contact@esotericsoftware.com, briefly mentioning this forum thread URL so that we know the context. Then we can have a look at it.

              If you can't reproduce the issue with a minimal project, it's likely a problem with your code, then you might want to check with a debugger what values are passed to the SetAnimation method calls and what values are actually set at the TrackEntry.

                Harald 为什么负跟踪时间不会成为问题?您应该通过那时将 TrackTime 限制为 0。您是否尝试过像这样限制 trackTime?trackTime = Mathf.Max(0f, trackTime)

                目前来看,我将TrackTime设为一个很小的负值,在表现上是没有问题的 (很小的负值意味着下一帧trackTime将变为正数)
                而我之所以这样做,是因为我要提前一帧设置新动画
                而我之所以要提前一帧设置新动画,是因为如果不提前的话,每次重新设置新动画时,那一帧的表现并没有立即刷新,也就是说动画转换会比预计的延迟一帧。虽然不知道为什么会这样,但是这是很致命的表现。

                  Harald In general it is never necessary to avoid setting loop to false and add animations multiple times to emulate the same behaviour. If you need this, something is wrong with your logic code. If you can create a minimal Unity project with minimal code that still shows your issues, we can have a look at what's going wrong. You can send the Unity project as zip package to [contact@esotericsoftware.com](mailto:contact@esotericsoftware.com), briefly mentioning this forum thread URL so that we know the context. Then we can have a look at it.

                  我的意思是我在同一时间通过 AddAnimation 这个方法添加了四个动画到同一轨道上,使得他们能够按顺序播放,但是只有最后一个动画播放完毕时才会调用OnComplete 回调

                  例如
                  AddAnimation(0, "idle1", false, 0);
                  AddAnimation(0, "play1", false, 0);
                  AddAnimation(0, "idle2", false, 0);
                  AddAnimation(0, "play2", false, 0);

                  只有当 "play2" 动画播放完毕时,才会触发OnComplete回调

                    Bali_Jun In order for us to understand your situation, it would be best if you could send us a minimal Unity project, as Harald has already suggested, but from what you have explained, it seems correct that the Complete event is only fired for the last animation. This is because the Complete event is not fired when the animation changes, but when the animation has been played to the end without being interrupted. Since you are setting the new animation one frame in advance, if the next animation is set, the previous animation will not be able to play the animation to the end, so it is expected that the Complete event will not be fired.

                    If you want to get the timing when the previous animation ends anyway, even if it is interrupted, you can use the End event.

                      Misaki
                      抱歉,发送一个迷你Unity项目可能不太方便,还望你们谅解,因为我已经找到了其他可用的解决方法,所以无论有没有找到最终原因,我都很感谢你们的每一次回答。

                      不过首先要注意的是,在这个问题中,我并没有提前一帧改变动画状态,而只是使用AddAnimation,让动画自己按照顺序播放,这中间并没有尝试打断动画的播放。SkeletonAnimation的更新则是使用的fixedFrame。

                      不过我已经通过调试发现了问题原因:
                      在AnimationState的QueueEvents中,判断bool类型参数complete时使用了"complete = animationTime >= animationEnd && entry.animationLast < animationEnd;",正常情况下,在动画播放完毕时,complete能够正常变为true,而在我的调试中,每次似乎都缺少一些时间时间以达到complete为true的条件。
                      换句话说,通过AddAnimation添加到播放队列中的动画,其变化的时机似乎不对?

                      以下是一段连续的打印方式与打印结果:
                      if (entry.loop)
                      complete = duration == 0 || (trackLastWrapped > entry.trackTime % duration);
                      else
                      complete = animationTime >= animationEnd && entry.animationLast < animationEnd;

                            Debug.LogError("AT:" + animationTime + "   End:" + animationEnd);
                            Debug.LogError("Last:" + entry.animationLast + "   End:" + animationEnd);

                      AT:0.89972 End:1
                      Last:0.88888 End:1

                      AT:0.91056 End:1
                      Last:0.89972 End:1

                      AT:0.9214 End:1
                      Last:0.91056 End:1

                      AT:0.13224 End:1
                      Last:0.1213999 End:1

                      而正常情况下,应该有类似以下的情况,来触发OnComplete
                      AT:1.01028 End:1
                      Last:0.9994399 End:1
                      可从上面的打印来看,似乎跳过了一段时间

                      @Bali_Jun Please understand that we can't debug your code just from reading some sections of code. One reason is that that's highly inefficient, another is that it's likely that relevant code parts and how everything is set up is missing. We require a Unity project to test what is going wrong. The callback should always behave as the documentation reads. If it does not, either it is a bug in your code, or an issue on our end (which we would like to fix then). We can't tell without a Unity reproduction project though. We hope you understand.

                        Bali_Jun 目前来看,我将TrackTime设为一个很小的负值,在表现上是没有问题的 (很小的负值意味着下一帧trackTime将变为正数)
                        而我之所以这样做,是因为我要提前一帧设置新动画
                        而我之所以要提前一帧设置新动画,是因为如果不提前的话,每次重新设置新动画时,那一帧的表现并没有立即刷新,也就是说动画转换会比预计的延迟一帧。虽然不知道为什么会这样,但是这是很致命的表现。

                        Please note that if you call SetAnimation(), the current animation will be mixed out immediately and replaced with the new animation, if you then set a negative TrackTime on the new animation, this sounds like an invalid state. This may lead to problems. If you want to set animations with a delay, there is AddAnimation() which accepts a delay parameter for this purpose.

                        Harald
                        非常抱歉,问题已经解决了... 问题出现在很早之前的代码,手动调用了组件AnimationState更新动画的时机与时间,使得其与现在的代码产生了冲突
                        我为浪费了你们一些时间而感到抱歉

                        至于回到SetUp状态的问题,虽然我仍不知道触发的原因(可能正是因为把TrackTime设置为负数造成的?),但是现在也已经完全解决了

                        再次表示感谢与抱歉

                        @Bali_Jun No need to apologize, glad it's no longer a problem. If you do encounter any similar issues in the future, please do send us a Unity project if it can be reproduced, we're always happy to fix any bugs on our side.