Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ default = []
custom_cursor = ["bevy/bevy_winit", "bevy/custom_cursor"]

[dependencies]
bevy = { version = "0.16.0", default-features = false, features = [
bevy = { version = "0.16", default-features = false, features = [
"bevy_log",
"bevy_pbr",
"bevy_sprite",
Expand All @@ -25,7 +25,7 @@ bevy = { version = "0.16.0", default-features = false, features = [

[dev-dependencies]
approx = "0.5.1"
bevy = { version = "0.16.0", default-features = true }
bevy = { version = "0.16", default-features = true }
clap = { version = "4.5.16", features = ["derive"] }
iyes_perf_ui = { git = "https://github.com/IyesGames/iyes_perf_ui.git", rev = "8e6dd6072c2e093b5353f62970e55304175dbe8f" }
rand = "0.9"
Expand Down
16 changes: 16 additions & 0 deletions src/animation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ pub struct Animation {
direction: Option<AnimationDirection>,
/// The optional easing of this animation
easing: Option<Easing>,
/// An optional key to group animations that are variants (e.g., different directions)
/// of the same conceptual animation. This won't reset the current frame duration when only
/// switching between animations with the same key.
pub animation_group_key: Option<String>,
}

impl Animation {
Expand All @@ -129,6 +133,7 @@ impl Animation {
repetitions: None,
direction: None,
easing: None,
animation_group_key: None,
}
}

Expand All @@ -140,6 +145,7 @@ impl Animation {
repetitions: None,
direction: None,
easing: None,
animation_group_key: None,
}
}

Expand Down Expand Up @@ -210,4 +216,14 @@ impl Animation {
self.easing = Some(easing);
self
}

pub fn with_group_key(mut self, key: impl Into<String>) -> Self {
self.animation_group_key = Some(key.into());
self
}

pub fn set_group_key(&mut self, key: impl Into<String>) -> &mut Self {
self.animation_group_key = Some(key.into());
self
}
}
165 changes: 96 additions & 69 deletions src/animator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,39 @@ impl Animator {
// Run animations for all the entities

for mut item in query.iter_mut() {
let current_component_animation_id = item.spritesheet_animation.animation_id;
let mut previous_accumulated_time = Duration::ZERO;
let mut is_directional_change_of_same_type = false;

// Check if this is a change of AnimationId for an existing instance
if let Some(existing_instance) = self.animation_instances.get(&item.entity) {
if existing_instance.animation_id != current_component_animation_id {
// AnimationId has changed. Let's see if it's a grouped change.
let old_animation_data = library.get_animation(existing_instance.animation_id);
let new_animation_data = library.get_animation(current_component_animation_id);

if old_animation_data.animation_group_key.is_some() && // Both must have a key
old_animation_data.animation_group_key == new_animation_data.animation_group_key
{
// It's a change within the same animation group (e.g., different direction)
is_directional_change_of_same_type = true;
previous_accumulated_time = existing_instance.accumulated_time;
}
}
}

// Create a new animation instance if:
let needs_new_animation_instance = match self.animation_instances.get(&item.entity) {
// The entity has an animation instance already but it switched animation
Some(instance) => instance.animation_id != item.spritesheet_animation.animation_id,
Some(instance) => instance.animation_id != current_component_animation_id,
// The entity has no animation instance yet
None => true,
};

if needs_new_animation_instance {
// Create a new iterator for this animation

let cache = library.get_animation_cache(item.spritesheet_animation.animation_id);
let cache = library.get_animation_cache(current_component_animation_id);

let mut iterator = AnimationIterator::new(cache.clone());

Expand All @@ -105,15 +126,21 @@ impl Animator {

// Create the instance and immediately play the first frame

let first_frame = Self::play_frame(&mut iterator, &mut item, event_writer);
let first_frame_data = Self::play_frame(&mut iterator, &mut item, event_writer);

let accumulated_time_for_new_instance = if is_directional_change_of_same_type {
previous_accumulated_time
} else {
Duration::ZERO
};

self.animation_instances.insert(
item.entity,
AnimationInstance {
animation_id: item.spritesheet_animation.animation_id,
animation_id: current_component_animation_id,
iterator,
current_frame: first_frame,
accumulated_time: Duration::ZERO,
current_frame: first_frame_data,
accumulated_time: accumulated_time_for_new_instance,
},
);
}
Expand All @@ -122,28 +149,24 @@ impl Animator {

// Apply manual progress updates

if animation_instance
.current_frame
.as_ref()
.filter(|frame| item.spritesheet_animation.progress != frame.1)
.is_some()
if let Some((ref _current_iterator_frame, current_iterator_actual_progress)) =
animation_instance.current_frame
{
if animation_instance
.iterator
.to(item.spritesheet_animation.progress)
{
Self::play_frame(&mut animation_instance.iterator, &mut item, event_writer)
.inspect(|new_frame| {
animation_instance.current_frame = Some(new_frame.clone());
animation_instance.accumulated_time = Duration::ZERO;
});
} else {
// Restore to the last valid progress if invalid
item.spritesheet_animation.progress = animation_instance
.current_frame
.as_ref()
.map(|(_, progress)| *progress)
.unwrap_or_default()
if item.spritesheet_animation.progress != current_iterator_actual_progress {
// User manually changed item.spritesheet_animation.progress
if animation_instance
.iterator
.to(item.spritesheet_animation.progress)
{
Self::play_frame(&mut animation_instance.iterator, &mut item, event_writer)
.inspect(|new_frame_data| {
animation_instance.current_frame = Some(new_frame_data.clone());
animation_instance.accumulated_time = Duration::ZERO;
});
} else {
// Restore to the last valid progress if user set an invalid one
item.spritesheet_animation.progress = current_iterator_actual_progress;
}
}
}

Expand All @@ -161,50 +184,54 @@ impl Animator {
time.delta_secs() * item.spritesheet_animation.speed_factor,
);

while let Some(current_frame) = animation_instance
.current_frame
.as_ref()
.filter(|frame| animation_instance.accumulated_time > frame.0.duration)
{
// Consume the elapsed time

animation_instance.accumulated_time -= current_frame.0.duration;

// Fetch the next frame

animation_instance.current_frame =
Self::play_frame(&mut animation_instance.iterator, &mut item, event_writer)
.or_else(|| {
// The animation is over

// Emit the end events if the animation just ended

event_writer.write(AnimationEvent::ClipRepetitionEnd {
entity: item.entity,
animation_id: animation_instance.animation_id,
clip_id: current_frame.0.clip_id,
clip_repetition: current_frame.0.clip_repetition,
while let Some(cf_data_tuple) = animation_instance.current_frame.as_ref() {
let frame_duration = cf_data_tuple.0.duration;

if animation_instance.accumulated_time >= frame_duration {
animation_instance.accumulated_time -= frame_duration;

// Store current frame info before advancing, for end events
let last_played_frame_data = cf_data_tuple.0.clone();

// Fetch the next frame

animation_instance.current_frame =
Self::play_frame(&mut animation_instance.iterator, &mut item, event_writer)
.or_else(|| {
event_writer.write(AnimationEvent::ClipRepetitionEnd {
entity: item.entity,
animation_id: animation_instance.animation_id,
clip_id: last_played_frame_data.clip_id,
clip_repetition: last_played_frame_data.clip_repetition,
});

event_writer.write(AnimationEvent::ClipEnd {
entity: item.entity,
animation_id: animation_instance.animation_id,
clip_id: last_played_frame_data.clip_id,
});

event_writer.write(AnimationEvent::AnimationRepetitionEnd {
entity: item.entity,
animation_id: animation_instance.animation_id,
animation_repetition: last_played_frame_data
.animation_repetition,
});

event_writer.write(AnimationEvent::AnimationEnd {
entity: item.entity,
animation_id: animation_instance.animation_id,
});

None
});

event_writer.write(AnimationEvent::ClipEnd {
entity: item.entity,
animation_id: animation_instance.animation_id,
clip_id: current_frame.0.clip_id,
});

event_writer.write(AnimationEvent::AnimationRepetitionEnd {
entity: item.entity,
animation_id: animation_instance.animation_id,
animation_repetition: current_frame.0.animation_repetition,
});

event_writer.write(AnimationEvent::AnimationEnd {
entity: item.entity,
animation_id: animation_instance.animation_id,
});

None
});
if animation_instance.current_frame.is_none() {
break; // Animation finished, exit while loop
}
} else {
break; // Not enough accumulated_time for the current frame
}
}
}
}
Expand Down