Animation events aren't fired for frame-by-frame control animations
- Dominant language
- Rust
- Stars
- 48.2k
- Forks
- 4.8k
- Avg merge
- 3d 16h
- Merged PRs (30d)
- 171
Description
_Note: This could be considered either a bug report or a feature request. I'm labeling this as a bug report because this used to work before #15677 landed._
tl;dr: Bevy animation events fire when animations are progressed via `play`, but not when progressed manually via `pause` + `seek_to`.
## Bevy version and features
0.17.0-rc-1, though this has existed since 0.16 or earlier as well.
## Background
There are two approaches to animation for games:
1. **Time-based animation**. This is the most common approach and is the one used in all of Bevy's example code. Given an animation, at some point a `.play()` method is called, and the animation automatically progresses over time, possibly on a loop or with a simple speed multiplier.
2. **Frame-by-frame animation**. Under this method, given an animation, some code that runs each frame manually sets the desired frame or timestamp.
Time-based animation is usually the go-to, but there are times when frame-by-frame animation is needed. If animations need to be frame-perfect, synchronized over the network, variable based on gameplay mechanics, procedural, or able to be reversible or skip frames, then animations need frame-by-frame control. For example, fighting games use this since the displayed animation frame needs to be perfectly in sync with hitboxes, network delays, etc. Or a platformer game might manually set "jump" animation frames based on the character's vertical velocity or distance from ground.
You can do time-based animations in Bevy using `AnimationPlayer`'s `play` method. Or, you can do frame-by-frame animations by keeping an animation _paused_ and using AnimationPlayer's `seek_to` or `set_seek_time` methods.
## What you did
I modified the `animated_mesh_events` example to use frame-by-frame animations instead of time-based animations to demonstrate the issue. See the code here: https://github.com/hansler/bevy/commit/e7ba0fe863f6ed2141696347ebadc9f3b3ca5b3c
Just to show a close-to-real-world example, I tied the animation's speed to a sin wave that varies from 10% to 300% over time.
## What went wrong
The animation works fine this way, but animation events don't fire. I think animation events (or at least #15677) are assuming that all animations done with AnimationPlayer are time-based, not frame-by-frame. If I revert the two lines of code changed in #15677 (which my branch does here: https://github.com/hansler/bevy/commit/e7ba0fe863f6ed2141696347ebadc9f3b3ca5b3c), then events work again.
Current state:
https://github.com/user-attachments/assets/2ec15743-1e1e-4c4a-90d7-effb81290a97
With #15677 reverted:
https://github.com/user-attachments/assets/205d78f9-134e-4536-81b1-53c170cc1d93
#15677 was intended to fix a different bug: paused animations causing events to fire every frame if paused at just the right moment. I think we need a way to fix that bug while still allowing "paused" animations to fire events so we can support animations which progress via `seek_to` rather than `play`.
As an aside, there is some API weirdness when manually advancing animations frame-by-frame that you can see in my example code. For example, you need to initially start the animation using `player.play(animations.index).pause();`, because `play` sets the animation as active (which we want) and also starts the animation (which we don't want). So beyond this bug I think there might be room for API improvements for this use-case.
Contributor guide
Research direction
Start with the modified `animated_mesh_events` example and the `AnimationPlayer` paths involving `play`, `pause`, `seek_to`, and `set_seek_time`. Compare the behavior introduced by #15677 with the linked reproduction commits. Done means animation events fire when paused animations advance manually, without firing repeatedly when a paused animation remains at the same time.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- game-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100