解决 spine 动画的 complete事件会触发多次的 bug #19041
Open
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
解决 spine 动画的 complete事件会触发多次的 bug.
其实目前 cocos 代码里是有防止 complete重复被调用的逻辑的:
只有
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:
Compatibility Check
This pull request:
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 ofAnimationState.cpp. After queueing events and updating the "next" animation tracking values (_nextAnimationLastand_nextTrackLast), the fix immediately synchronizes these with their current counterparts (_animationLastand_trackLast). This ensures that the completion detection logic inqueueEvents()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
Confidence score: 3/5
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.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"