feat(animation):

* moved triggering of frame marker events to the blueprints crate
 * added additional handling of frame markers for blueprint animations (yikes is this convoluted)
 * added additional animated blueprint for testing blueprint animation markers
This commit is contained in:
kaosat.dev 2024-04-01 00:26:23 +02:00
parent 49dd0bc536
commit 66df7fae99
6 changed files with 192 additions and 68 deletions

View File

@ -80,3 +80,148 @@ pub struct AnimationMarkerReached {
pub frame: u32, pub frame: u32,
pub marker_name: String, pub marker_name: String,
} }
/////////////////////
/// triggers events when a given animation marker is reached for INSTANCE animations
pub fn trigger_instance_animation_markers_events(
animation_infos: Query<(
Entity,
&AnimationMarkers,
&InstanceAnimationPlayerLink,
&InstanceAnimations,
&AnimationInfos,
)>,
animation_players: Query<&AnimationPlayer>,
animation_clips: Res<Assets<AnimationClip>>,
mut animation_marker_events: EventWriter<AnimationMarkerReached>,
) {
for (entity, markers, link, animations, animation_infos) in animation_infos.iter() {
let animation_player = animation_players.get(link.0).unwrap();
let animation_clip = animation_clips.get(animation_player.animation_clip());
if animation_clip.is_some() {
// println!("Entity {:?} markers {:?}", entity, markers);
// println!("Player {:?} {}", animation_player.elapsed(), animation_player.completions());
// FIMXE: yikes ! very inneficient ! perhaps add boilerplate to the "start playing animation" code so we know what is playing
let animation_name = animations.named_animations.iter().find_map(|(key, value)| {
if value == animation_player.animation_clip() {
Some(key)
} else {
None
}
});
if animation_name.is_some() {
let animation_name = animation_name.unwrap();
let animation_length_seconds = animation_clip.unwrap().duration();
let animation_length_frames = animation_infos
.animations
.iter()
.find(|anim| &anim.name == animation_name)
.unwrap()
.frames_length;
// TODO: we also need to take playback speed into account
let time_in_animation = animation_player.elapsed()
- (animation_player.completions() as f32) * animation_length_seconds;
let frame_seconds =
(animation_length_frames as f32 / animation_length_seconds) * time_in_animation;
let frame = frame_seconds as u32;
let matching_animation_marker = &markers.0[animation_name];
if matching_animation_marker.contains_key(&frame) {
let matching_markers_per_frame = matching_animation_marker.get(&frame).unwrap();
// println!("FOUND A MARKER {:?} at frame {}", matching_markers_per_frame, frame);
// emit an event AnimationMarkerReached(entity, animation_name, frame, marker_name)
// FIXME: problem, this can fire multiple times in a row, depending on animation length , speed , etc
for marker_name in matching_markers_per_frame {
animation_marker_events.send(AnimationMarkerReached {
entity: entity,
animation_name: animation_name.clone(),
frame: frame,
marker_name: marker_name.clone(),
});
}
}
}
}
}
}
/// triggers events when a given animation marker is reached for BLUEPRINT animations
pub fn trigger_blueprint_animation_markers_events(
animation_infos: Query<(
Entity,
&BlueprintAnimationPlayerLink,
&BlueprintAnimations,
)>,
// FIXME: annoying hiearchy issue yet again: the Markers & AnimationInfos are stored INSIDE the blueprint, so we need to access them differently
all_animation_infos: Query<(Entity, &AnimationMarkers, &AnimationInfos, &Parent)>,
animation_players: Query<&AnimationPlayer>,
animation_clips: Res<Assets<AnimationClip>>,
mut animation_marker_events: EventWriter<AnimationMarkerReached>,
) {
for (entity, link, animations) in animation_infos.iter() {
let animation_player = animation_players.get(link.0).unwrap();
let animation_clip = animation_clips.get(animation_player.animation_clip());
// FIXME: horrible code
let mut markers:Option<&AnimationMarkers>= None;
let mut animation_infos:Option<&AnimationInfos>=None;
for (_, _markers, _animation_infos, parent) in all_animation_infos.iter(){
if parent.get() == entity {
markers = Some(_markers);
animation_infos = Some(_animation_infos);
break;
}
}
if animation_clip.is_some() && markers.is_some() && animation_infos.is_some() {
let markers = markers.unwrap();
let animation_infos = animation_infos.unwrap();
// println!("Entity {:?} markers {:?}", entity, markers);
// println!("Player {:?} {}", animation_player.elapsed(), animation_player.completions());
// FIMXE: yikes ! very inneficient ! perhaps add boilerplate to the "start playing animation" code so we know what is playing
let animation_name = animations.named_animations.iter().find_map(|(key, value)| {
if value == animation_player.animation_clip() {
Some(key)
} else {
None
}
});
if animation_name.is_some() {
let animation_name = animation_name.unwrap();
let animation_length_seconds = animation_clip.unwrap().duration();
let animation_length_frames = animation_infos
.animations
.iter()
.find(|anim| &anim.name == animation_name)
.unwrap()
.frames_length;
// TODO: we also need to take playback speed into account
let time_in_animation = animation_player.elapsed()
- (animation_player.completions() as f32) * animation_length_seconds;
let frame_seconds =
(animation_length_frames as f32 / animation_length_seconds) * time_in_animation;
let frame = frame_seconds as u32;
let matching_animation_marker = &markers.0[animation_name];
if matching_animation_marker.contains_key(&frame) {
let matching_markers_per_frame = matching_animation_marker.get(&frame).unwrap();
// println!("FOUND A MARKER {:?} at frame {}", matching_markers_per_frame, frame);
// emit an event AnimationMarkerReached(entity, animation_name, frame, marker_name)
// FIXME: problem, this can fire multiple times in a row, depending on animation length , speed , etc
for marker_name in matching_markers_per_frame {
animation_marker_events.send(AnimationMarkerReached {
entity: entity,
animation_name: animation_name.clone(),
frame: frame,
marker_name: marker_name.clone(),
});
}
}
}
}
}
}

View File

@ -177,6 +177,11 @@ impl Plugin for BlueprintsPlugin {
(spawned_blueprint_post_process, apply_deferred) (spawned_blueprint_post_process, apply_deferred)
.chain() .chain()
.in_set(GltfBlueprintsSet::AfterSpawn), .in_set(GltfBlueprintsSet::AfterSpawn),
); )
.add_systems(Update, (trigger_instance_animation_markers_events, trigger_blueprint_animation_markers_events))
;
} }
} }

View File

@ -2993,6 +2993,17 @@
"type": "object", "type": "object",
"typeInfo": "Struct" "typeInfo": "Struct"
}, },
"bevy_example::game::animation::MarkerFox": {
"additionalProperties": false,
"isComponent": true,
"isResource": false,
"properties": {},
"required": [],
"short_name": "MarkerFox",
"title": "bevy_example::game::animation::MarkerFox",
"type": "object",
"typeInfo": "Struct"
},
"bevy_example::test_components::AComponentWithAnExtremlyExageratedOrMaybeNotButCouldBeNameOrWut": { "bevy_example::test_components::AComponentWithAnExtremlyExageratedOrMaybeNotButCouldBeNameOrWut": {
"additionalProperties": false, "additionalProperties": false,
"isComponent": true, "isComponent": true,

View File

@ -24,6 +24,11 @@ pub struct Marker2;
/// flag component for testing /// flag component for testing
pub struct Marker3; pub struct Marker3;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// flag component for testing
pub struct MarkerFox;
#[derive(Resource)] #[derive(Resource)]
pub struct AnimTest(Handle<Gltf>); pub struct AnimTest(Handle<Gltf>);
@ -91,9 +96,32 @@ pub fn play_animations(
(With<AnimationInfos>, With<Marker3>), (With<AnimationInfos>, With<Marker3>),
>, >,
animated_fox: Query<
(&BlueprintAnimationPlayerLink, &BlueprintAnimations),
(With<MarkerFox>),
>,
mut animation_players: Query<&mut AnimationPlayer>, mut animation_players: Query<&mut AnimationPlayer>,
keycode: Res<ButtonInput<KeyCode>>, keycode: Res<ButtonInput<KeyCode>>,
) { ) {
if keycode.just_pressed(KeyCode::KeyP) {
for (link, animations) in animated_fox.iter() {
println!("animations {:?}", animations.named_animations);
let mut animation_player = animation_players.get_mut(link.0).unwrap();
let anim_name = "Run";
animation_player
.play_with_transition(
animations
.named_animations
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(5),
)
.repeat();
}
}
if keycode.just_pressed(KeyCode::KeyM) { if keycode.just_pressed(KeyCode::KeyM) {
for (link, animations) in animated_marker1.iter() { for (link, animations) in animated_marker1.iter() {
println!("animations {:?}", animations.named_animations); println!("animations {:?}", animations.named_animations);
@ -202,72 +230,6 @@ pub fn play_animations(
} }
} }
pub fn trigger_event_based_on_animation_marker(
animation_infos: Query<(
Entity,
&AnimationMarkers,
&InstanceAnimationPlayerLink,
&InstanceAnimations,
&AnimationInfos,
)>,
animation_players: Query<&AnimationPlayer>,
animation_clips: Res<Assets<AnimationClip>>,
mut animation_marker_events: EventWriter<AnimationMarkerReached>,
) {
for (entity, markers, link, animations, animation_infos) in animation_infos.iter() {
let animation_player = animation_players.get(link.0).unwrap();
let animation_clip = animation_clips.get(animation_player.animation_clip());
if animation_clip.is_some() {
// if marker_trackers.0.contains_key(k)
// marker_trackers.0
// println!("Entity {:?} markers {:?}", entity, markers);
// println!("Player {:?} {}", animation_player.elapsed(), animation_player.completions());
// FIMXE: yikes ! very inneficient ! perhaps add boilerplate to the "start playing animation" code so we know what is playing
let animation_name = animations.named_animations.iter().find_map(|(key, value)| {
if value == animation_player.animation_clip() {
Some(key)
} else {
None
}
});
if animation_name.is_some() {
let animation_name = animation_name.unwrap();
let animation_length_seconds = animation_clip.unwrap().duration();
let animation_length_frames = animation_infos
.animations
.iter()
.find(|anim| &anim.name == animation_name)
.unwrap()
.frames_length;
// TODO: we also need to take playback speed into account
let time_in_animation = animation_player.elapsed()
- (animation_player.completions() as f32) * animation_length_seconds;
let frame_seconds =
(animation_length_frames as f32 / animation_length_seconds) * time_in_animation;
let frame = frame_seconds as u32;
let matching_animation_marker = &markers.0[animation_name];
if matching_animation_marker.contains_key(&frame) {
let matching_markers_per_frame = matching_animation_marker.get(&frame).unwrap();
// println!("FOUND A MARKER {:?} at frame {}", matching_markers_per_frame, frame);
//emit an event , something like AnimationMarkerReached(entity, animation_name, frame, marker_name)
// FIXME: problem, this can fire multiple times in a row, depending on animation length , speed , etc
for marker_name in matching_markers_per_frame {
animation_marker_events.send(AnimationMarkerReached {
entity: entity,
animation_name: animation_name.clone(),
frame: frame,
marker_name: marker_name.clone(),
});
}
}
}
}
}
}
pub fn react_to_animation_markers( pub fn react_to_animation_markers(
mut animation_marker_events: EventReader<AnimationMarkerReached>, mut animation_marker_events: EventReader<AnimationMarkerReached>,

View File

@ -130,6 +130,7 @@ impl Plugin for GamePlugin {
app.register_type::<Marker1>() app.register_type::<Marker1>()
.register_type::<Marker2>() .register_type::<Marker2>()
.register_type::<Marker3>() .register_type::<Marker3>()
.register_type::<MarkerFox>()
.add_systems(Update, (spawn_test).run_if(in_state(GameState::InGame))) .add_systems(Update, (spawn_test).run_if(in_state(GameState::InGame)))
.add_systems(Update, validate_export) .add_systems(Update, validate_export)
@ -137,7 +138,7 @@ impl Plugin for GamePlugin {
.add_systems(OnEnter(AppState::AppRunning), setup_game) .add_systems(OnEnter(AppState::AppRunning), setup_game)
.add_systems(OnEnter(AppState::MenuRunning), setup_main_scene_animations) .add_systems(OnEnter(AppState::MenuRunning), setup_main_scene_animations)
.add_systems(Update, (animations, trigger_event_based_on_animation_marker) .add_systems(Update, (animations)
.run_if(in_state(AppState::AppRunning)) .run_if(in_state(AppState::AppRunning))
.after(GltfBlueprintsSet::AfterSpawn) .after(GltfBlueprintsSet::AfterSpawn)
) )