diff --git a/crates/bevy_gltf_blueprints/src/animation.rs b/crates/bevy_gltf_blueprints/src/animation.rs index 1257044..841b475 100644 --- a/crates/bevy_gltf_blueprints/src/animation.rs +++ b/crates/bevy_gltf_blueprints/src/animation.rs @@ -34,4 +34,14 @@ pub struct InstanceAnimations { /// so that the root entity knows which of its children contains an actualy `AnimationPlayer` component /// this is for convenience, because currently , Bevy's gltf parsing inserts `AnimationPlayers` "one level down" /// ie armature/root for animated models, which means more complex queries to trigger animations that we want to avoid -pub struct InstanceAnimationPlayerLink(pub Entity); \ No newline at end of file +pub struct InstanceAnimationPlayerLink(pub Entity); + + +pub struct AnimationMarker{ + pub frame:u32, + pub name: String, +} + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +pub struct AnimationMarkers(pub HashMap >>); \ No newline at end of file diff --git a/crates/bevy_gltf_blueprints/src/lib.rs b/crates/bevy_gltf_blueprints/src/lib.rs index e18daf6..04becf0 100644 --- a/crates/bevy_gltf_blueprints/src/lib.rs +++ b/crates/bevy_gltf_blueprints/src/lib.rs @@ -123,6 +123,10 @@ impl Plugin for BlueprintsPlugin { .register_type::() .register_type::() .register_type::() + .register_type::() + .register_type::>>() + .register_type:: >>>() + .register_type::() .register_type::>() diff --git a/testing/bevy_example/assets/testing.blend b/testing/bevy_example/assets/testing.blend index ad9c366..5a3efeb 100644 Binary files a/testing/bevy_example/assets/testing.blend and b/testing/bevy_example/assets/testing.blend differ diff --git a/testing/bevy_example/src/game/mod.rs b/testing/bevy_example/src/game/mod.rs index 00187ad..977d862 100644 --- a/testing/bevy_example/src/game/mod.rs +++ b/testing/bevy_example/src/game/mod.rs @@ -3,7 +3,7 @@ use std::{ collections::HashMap, fs, time::Duration }; -use bevy_gltf_blueprints::{Animated, BlueprintAnimationPlayerLink, BlueprintAnimations, InstanceAnimationPlayerLink, InstanceAnimations, BlueprintName, BlueprintsList, GltfBlueprintsSet}; +use bevy_gltf_blueprints::{Animated, AnimationMarkers, BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintName, BlueprintsList, GltfBlueprintsSet, InstanceAnimationPlayerLink, InstanceAnimations}; pub use in_game::*; use bevy::{ @@ -308,6 +308,39 @@ fn play_animations( } } +fn trigger_event_based_on_animation_marker( + bla: Query<(Entity, &AnimationMarkers, &InstanceAnimationPlayerLink, &InstanceAnimations)>, + animation_players: Query<&AnimationPlayer>, + animation_clips: Res> +) { + for (entity, markers, link, animations) in bla.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()); + + let animation_total_length = animation_clip.unwrap().duration(); + let animation_total_frames = 80; // FIXME just for testing + // TODO: we also need to take playback speed into account + let time_in_animation = animation_player.elapsed() - (animation_player.completions() as f32) * animation_total_length;//(animation_player.elapsed() / (animation_player.completions() as f32 + 1.0)) ;// / animation_total_length; + let time_bla = (animation_total_frames as f32 / animation_total_length) * time_in_animation ; + let frame = time_bla as u32; + // println!("time_in_animation {} out of {}, completions {}, // frame {}",time_in_animation, animation_total_length, animation_player.completions(), frame); + //animation_player.animation_clip() + + let matching_animation_marker = &markers.0[&"Blueprint1_jump".to_string()]; + 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); + } + } + + } +} + #[derive(Component, Reflect, Default, Debug)] #[reflect(Component)] /// flag component for testing @@ -336,7 +369,7 @@ impl Plugin for GamePlugin { .add_systems(OnEnter(AppState::AppRunning), setup_game) .add_systems(OnEnter(AppState::MenuRunning), setup_main_scene_animations) - .add_systems(Update, animations + .add_systems(Update, (animations, trigger_event_based_on_animation_marker) .run_if(in_state(AppState::AppRunning)) .after(GltfBlueprintsSet::AfterSpawn) ) diff --git a/tools/gltf_auto_export/helpers/helpers_scenes.py b/tools/gltf_auto_export/helpers/helpers_scenes.py index 57e2654..1beca2c 100644 --- a/tools/gltf_auto_export/helpers/helpers_scenes.py +++ b/tools/gltf_auto_export/helpers/helpers_scenes.py @@ -56,8 +56,22 @@ def copy_animation_data(source, target): # sort animations alphabetically (case insensitive) so they have a defined order and match Blender's Action list blender_actions.sort(key = lambda a: a.name.lower()) + markers_per_animation = {} for action in blender_actions: - animations.append(blender_tracks[action.name]) + animation_name = blender_tracks[action.name] + animations.append(animation_name) + + markers_per_animation[animation_name] = {} + + print("markers", action.pose_markers, "for", action.name) + for marker in action.pose_markers: + + if marker.frame not in markers_per_animation[animation_name]: + markers_per_animation[animation_name][marker.frame] = [] + print(" marker", marker.name, marker.frame) + + markers_per_animation[animation_name][marker.frame].append(marker.name) + print("animations", animations) """if target.animation_data == None: @@ -67,13 +81,29 @@ def copy_animation_data(source, target): with bpy.context.temp_override(active_object=source, selected_editable_objects=[target]): bpy.ops.object.make_links_data(type='ANIMATION') # we add an "animated" flag component - target['Animated'] = f'(animations: {animations})'.replace("'", '"') #'(animations: [])' # + target['Animated'] = f'(animations: {animations})'.replace("'", '"') + + markers_formated = '{' + for animation in markers_per_animation.keys(): + markers_formated += f'"{animation}":' + markers_formated += "{" + for frame in markers_per_animation[animation].keys(): + markers = markers_per_animation[animation][frame] + markers_formated += f"{frame}:{markers}, ".replace("'", '"') + markers_formated += '}, ' + markers_formated += '}' + print("markers_formated", markers_formated) + target["AnimationMarkers"] = f'( {markers_formated} )' + #'({"animation_name": {5: ["Marker_1"]} })' + #f'({json.dumps(markers_per_animation)})' """print("copying animation data for", source.name, target.animation_data) properties = [p.identifier for p in source.animation_data.bl_rna.properties if not p.is_readonly] for prop in properties: print("copying stuff", prop) setattr(target.animation_data, prop, getattr(source.animation_data, prop))""" + + def duplicate_object(object, parent, combine_mode, destination_collection, library_collections, legacy_mode, nester=""): copy = None @@ -95,6 +125,10 @@ def duplicate_object(object, parent, combine_mode, destination_collection, libra children_per_collection = {} get_sub_collections([object.instance_collection], root_node, children_per_collection) empty_obj["BlueprintsList"] = f"({json.dumps(dict(children_per_collection))})" + + # empty_obj["AnimationMarkers"] = '({"animation_name": {5: "Marker_1"} })' + + #'({5: "sdf"})'#.replace('"',"'") #f"({json.dumps(dict(animation_foo))})" #empty_obj["Assets"] = {"Animations": [], "Materials": [], "Models":[], "Textures":[], "Audio":[], "Other":[]} # we copy custom properties over from our original object to our empty @@ -109,10 +143,7 @@ def duplicate_object(object, parent, combine_mode, destination_collection, libra object.name = original_name + "____bak" copy = object.copy() copy.name = original_name - # FIXME: orphan data comes from this one, not even sure if this copying is needed at all - """if object.data: - data = object.data.copy() - obj_copy.data = data""" + destination_collection.objects.link(copy)