cocos-engine icon indicating copy to clipboard operation
cocos-engine copied to clipboard

解决 spine 动画的 complete事件会触发多次的 bug

Open finscn opened this issue 2 months ago • 1 comments

解决 spine 动画的 complete事件会触发多次的 bug.

其实目前 cocos 代码里是有防止 complete重复被调用的逻辑的:

    // Queue complete if completed a loop iteration or the animation.
    bool complete = false;
    if (entry->_loop)
        complete = duration == 0 || (trackLastWrapped > MathUtil::fmod(entry->_trackTime, duration));
    else
        complete = animationTime >= animationEnd && entry->_animationLast < animationEnd;
    if (complete) _queue->complete(entry);

只有 entry->_animationLast < animationEnd 时才会触发 complete .

不过目前cocos 的代码是在 AnimationState.update() 方法中去更新 trackEntry 的 _animationLast 和 _trackLast 属性. 然后在 AnimationState.apply() 方法中去尝试触发的事件.

这就要求 AnimationState.update() 和 AnimationState.apply() 必须成对出现 才可以. 但是实际情况是, 某些场景下, 会出现单独执行 AnimationState.apply() 的情况.

所以这个 pr 改成, 在 AnimationState.apply() 后 就更新 trackEntry 的 _animationLast 和 _trackLast 属性.

反正这个 pr是解决了我们项目中遇到的 偶尔(某些场景下) complete事件会触发多次的 bug, 但是不确定会不会引起其他问题. 请 cocos 官方验证.

Re: #

Changelog


Continuous Integration

This pull request:

  • [ ] needs automatic test cases check.

    Manual trigger with @cocos-robot run test cases afterward.

  • [ ] does not change any runtime related code or build configuration

    If any reviewer thinks the CI checks are needed, please uncheck this option, then close and reopen the issue.


Compatibility Check

This pull request:

  • [ ] changes public API, and have ensured backward compatibility with deprecated features.
  • [ ] affects platform compatibility, e.g. system version, browser version, platform sdk version, platform toolchain, language version, hardware compatibility etc.
  • [ ] affects file structure of the build package or build configuration which requires user project upgrade.
  • [ ] introduces breaking changes, please list all changes, affected features and the scope of violation.

Greptile Overview

Updated On: 2025-10-20 08:36:37 UTC

Greptile Summary

This PR fixes a bug where Spine animation complete events fire multiple times by adding synchronization logic in the apply() method of AnimationState.cpp. After queueing events and updating the "next" animation tracking values (_nextAnimationLast and _nextTrackLast), the fix immediately synchronizes these with their current counterparts (_animationLast and _trackLast). This ensures that the completion detection logic in queueEvents() doesn't repeatedly trigger for the same animation completion across multiple frames. The Spine animation system uses these tracking variables to determine when an animation completes by comparing current time against the last processed time - without proper synchronization, the same completion condition could be satisfied multiple times.

Important Files Changed

Changed Files
Filename Score Overview
native/cocos/editor-support/spine/3.8/spine/AnimationState.cpp 3/5 Added immediate synchronization of animation tracking variables after queueing events to prevent duplicate complete event triggers

Confidence score: 3/5

  • This PR addresses a specific bug but introduces potential timing conflicts with existing synchronization logic.
  • Score reflects concerns about duplicated synchronization logic that already exists in the update() method (lines 357-358), which could cause subtle timing issues or interfere with the frame-to-frame state tracking that the animation system relies on for proper event detection. The fix may work for the immediate bug but could have unintended side effects on animation playback timing.
  • Pay close attention to native/cocos/editor-support/spine/3.8/spine/AnimationState.cpp - specifically test scenarios with looping animations, non-looping animations completing, and rapid apply() calls to ensure the immediate synchronization doesn't break the intended frame-based event detection mechanism.

Sequence Diagram

sequenceDiagram
    participant User
    participant AnimationState
    participant EventQueue
    participant TrackEntry
    participant Skeleton
    participant Listener

    User->>AnimationState: "update(delta)"
    AnimationState->>AnimationState: "Process each track entry"
    AnimationState->>TrackEntry: "Update track time and animation time"
    
    alt Track reaches end and no next entry
        AnimationState->>EventQueue: "end(entry)"
        AnimationState->>AnimationState: "disposeNext(entry)"
    end
    
    AnimationState->>EventQueue: "drain()"
    
    User->>AnimationState: "apply(skeleton)"
    AnimationState->>AnimationState: "animationsChanged() if needed"
    
    loop For each track entry
        AnimationState->>TrackEntry: "applyMixingFrom() if mixing"
        AnimationState->>Skeleton: "Apply timelines to skeleton"
        AnimationState->>AnimationState: "queueEvents(entry, animationTime)"
    end
    
    AnimationState->>EventQueue: "drain()"
    EventQueue->>EventQueue: "Process queued events"
    
    loop For each event in queue
        alt Event is Start/Interrupt/Complete
            EventQueue->>Listener: "Track entry listener callback"
            EventQueue->>Listener: "State listener callback"
        else Event is End
            EventQueue->>Listener: "Track entry listener callback"
            EventQueue->>Listener: "State listener callback"
            EventQueue->>Listener: "Dispose callback (track entry)"
            EventQueue->>Listener: "Dispose callback (state)"
            EventQueue->>TrackEntry: "reset()"
        else Event is custom Event
            EventQueue->>Listener: "Event callback with event data"
        end
    end
    
    EventQueue->>EventQueue: "Clear event queue"

finscn avatar Oct 20 '25 08:10 finscn

别人也有遇到类似问题的 : https://github.com/cocos/cocos-engine/issues/18525

finscn avatar Oct 20 '25 08:10 finscn