解决 spine 动画的 complete事件会触发多次的 bug
解决 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 casesafterward. - [ ] 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"
别人也有遇到类似问题的 : https://github.com/cocos/cocos-engine/issues/18525