Compare commits

...

2 Commits

Author SHA1 Message Date
kaosat.dev 5f955c1a53 feat(Blenvy:Bevy): overhauled & added back blueprint animation markers & co handling
* still messy, but way better
 * also worked on adding back the same feature for scene/instance animations
2024-07-18 00:22:18 +02:00
kaosat.dev ec7dc2cb48 chore(Blenvy): tweaks, adjustements, moved examples etc 2024-07-17 23:15:36 +02:00
60 changed files with 126 additions and 109 deletions

View File

@ -32,7 +32,7 @@ It also allows you to setup 'blueprints' in Blender by using collections (the re
One crate to rule them all ! One crate to rule them all !
- [blenvy](./crates/blenvu/) This crate allows you to - [blenvy](./crates/blenvy/) This crate allows you to
* define components direclty inside gltf files and instanciate/inject the components on the Bevy side. * define components direclty inside gltf files and instanciate/inject the components on the Bevy side.
* export your project's Bevy registry to json, in order to be able to generate custom component UIs on the Blender side in the Blender [blenvy](./tools/blenvy/README.md) add-on * export your project's Bevy registry to json, in order to be able to generate custom component UIs on the Blender side in the Blender [blenvy](./tools/blenvy/README.md) add-on
* define Blueprints/Prefabs for Bevy inside gltf files and spawn them in Bevy. With the ability to override and add components when spawning, efficient "level" loading etc * define Blueprints/Prefabs for Bevy inside gltf files and spawn them in Bevy. With the ability to override and add components when spawning, efficient "level" loading etc

View File

@ -301,7 +301,8 @@ Bevy Side:
- [x] remove "Library" component & co - [x] remove "Library" component & co
- [x] make "InBlueprint" non optional, - [x] make "InBlueprint" non optional,
- [ ] and perhaps rename it to "FromBlueprint(BlueprintInfo)" - [x] and perhaps rename it to "FromBlueprint"
- [ ] perhaps change it to FromBlueprint(BlueprintInfo)
- [x] BlueprintInstanceDisabled => BlueprintInstanceDisabled - [x] BlueprintInstanceDisabled => BlueprintInstanceDisabled
- [x] fix "remove component" operator from the rename/fix/update components panel - [x] fix "remove component" operator from the rename/fix/update components panel

View File

@ -199,7 +199,7 @@ There is also a ```BluePrintBundle``` for convenience , which just has
## Additional information ## Additional information
- When a blueprint is spawned, an ```InBlueprint``` component is inserted into all its children entities (and nested children etc) - When a blueprint is spawned, an ```FromBlueprint``` component is inserted into all its children entities (and nested children etc)
- this crate also provides a special optional ```GameWorldTag``` component: this is useful when you want to keep all your spawned entities inside a root entity - this crate also provides a special optional ```GameWorldTag``` component: this is useful when you want to keep all your spawned entities inside a root entity
You can use it in your queries to add your entities as children of this "world" You can use it in your queries to add your entities as children of this "world"

View File

@ -17,6 +17,11 @@ pub struct BlueprintAnimations {
/// ie armature/root for animated models, which means more complex queries to trigger animations that we want to avoid /// ie armature/root for animated models, which means more complex queries to trigger animations that we want to avoid
pub struct BlueprintAnimationPlayerLink(pub Entity); pub struct BlueprintAnimationPlayerLink(pub Entity);
#[derive(Component, Debug)]
/// Same as the above but for `AnimationInfos` components which get added (on the Blender side) to the entities that actually have the animations
/// which often is not the Blueprint or blueprint instance entity itself.
pub struct BlueprintAnimationInfosLink(pub Entity);
#[derive(Component, Reflect, Default, Debug)] #[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)] #[reflect(Component)]
/// storage for scene level animations for a given entity (hierarchy), essentially a clone of gltf's `named_animations` /// storage for scene level animations for a given entity (hierarchy), essentially a clone of gltf's `named_animations`
@ -33,6 +38,11 @@ pub struct SceneAnimations {
/// ie armature/root for animated models, which means more complex queries to trigger animations that we want to avoid /// ie armature/root for animated models, which means more complex queries to trigger animations that we want to avoid
pub struct SceneAnimationPlayerLink(pub Entity); pub struct SceneAnimationPlayerLink(pub Entity);
#[derive(Component, Debug)]
/// Same as the above but for scene's `AnimationInfos` components which get added (on the Blender side) to the entities that actually have the animations
/// which often is not the Blueprint or blueprint instance entity itself.
pub struct SceneAnimationInfosLink(pub Entity);
/// Stores Animation information: name, frame informations etc /// Stores Animation information: name, frame informations etc
#[derive(Reflect, Default, Debug)] #[derive(Reflect, Default, Debug)]
pub struct AnimationInfo { pub struct AnimationInfo {
@ -77,7 +87,75 @@ pub struct AnimationMarkerReached {
///////////////////// /////////////////////
/*
/// triggers events when a given animation marker is reached for BLUEPRINT animations
pub fn trigger_blueprint_animation_markers_events(
animation_data: Query<(Entity, &BlueprintAnimationPlayerLink, &BlueprintAnimationInfosLink, &BlueprintAnimations)>,
// FIXME: annoying hiearchy issue yet again: the Markers & AnimationInfos are stored INSIDE the blueprint, so we need to access them differently
animation_infos: Query<(&AnimationInfos, &AnimationMarkers)>,
animation_players: Query<&AnimationPlayer>,
mut animation_marker_events: EventWriter<AnimationMarkerReached>,
animation_clips: Res<Assets<AnimationClip>>,
) {
for (entity, player_link, infos_link, animations) in animation_data.iter() {
for (animation_name, node_index) in animations.named_indices.iter() {
let animation_player = animation_players.get(player_link.0).unwrap();
let (animation_infos, animation_markers) = animation_infos.get(infos_link.0).unwrap();
if animation_player.animation_is_playing(*node_index) {
if let Some(animation) = animation_player.animation(*node_index) {
// animation.speed()
// animation.completions()
if let Some(animation_clip_handle) = animations.named_animations.get(animation_name) {
if let Some(animation_clip) = animation_clips.get(animation_clip_handle) {
let animation_length_seconds = animation_clip.duration();
let animation_length_frames = animation_infos // FIXME: horribly inneficient
.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.elapsed()
- (animation.completions() as f32) * animation_length_seconds;
let frame_seconds = (animation_length_frames / animation_length_seconds)
* time_in_animation;
// println!("frame seconds {}", frame_seconds);
let frame = frame_seconds.ceil() as u32; // FIXME , bad hack
let matching_animation_marker = &animation_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);
// FIXME: complete hack-ish solution , otherwise this can fire multiple times in a row, depending on animation length , speed , etc
let diff = frame as f32 - frame_seconds;
if diff < 0.1 {
for marker_name in matching_markers_per_frame {
animation_marker_events.send(AnimationMarkerReached {
entity,
animation_name: animation_name.clone(),
frame,
marker_name: marker_name.clone(),
});
}
}
}
}
}
}
}
}
}
}
/// triggers events when a given animation marker is reached for INSTANCE animations /// triggers events when a given animation marker is reached for INSTANCE animations
pub fn trigger_instance_animation_markers_events( pub fn trigger_instance_animation_markers_events(
animation_infos: Query<( animation_infos: Query<(
@ -87,14 +165,30 @@ pub fn trigger_instance_animation_markers_events(
&SceneAnimations, &SceneAnimations,
&AnimationInfos, &AnimationInfos,
)>, )>,
animation_players: Query<(&AnimationPlayer)>, animation_players: Query<&AnimationPlayer>,
animation_clips: Res<Assets<AnimationClip>>, animation_clips: Res<Assets<AnimationClip>>,
animation_graphs: Res<Assets<AnimationGraph>>, animation_graphs: Res<Assets<AnimationGraph>>,
mut animation_marker_events: EventWriter<AnimationMarkerReached>, mut animation_marker_events: EventWriter<AnimationMarkerReached>,
) { ) {
for (entity, markers, link, animations, animation_infos) in animation_infos.iter() { for (entity, markers, player_link, animations, animation_infos) in animation_infos.iter() {
let animation_player = animation_players.get(link.0).unwrap(); //let (animation_player, animation_transitions) = animation_players.get(player_link.0).unwrap();
let animation_clip = animation_clips.get(animation_player.animation_clip()); //let foo = animation_transitions.get_main_animation().unwrap();
for (animation_name, node_index) in animations.named_indices.iter() {
let animation_player = animation_players.get(player_link.0).unwrap();
if animation_player.animation_is_playing(*node_index) {
if let Some(animation) = animation_player.animation(*node_index) {
if let Some(animation_clip_handle) = animations.named_animations.get(animation_name) {
if let Some(animation_clip) = animation_clips.get(animation_clip_handle) {
println!("helooo")
}
}
}
}
}
/*let animation_clip = animation_clips.get(animation_player.animation_clip());
// animation_player.play(animation) // animation_player.play(animation)
if animation_clip.is_some() { if animation_clip.is_some() {
@ -144,82 +238,6 @@ pub fn trigger_instance_animation_markers_events(
} }
} }
} }
} }*/
} }
} }
/// 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
for (_, markers, animation_infos, parent) in all_animation_infos.iter() {
if parent.get() == entity {
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 / animation_length_seconds)
* time_in_animation;
// println!("frame seconds {}", frame_seconds);
let frame = frame_seconds.ceil() as u32; // FIXME , bad hack
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: complete hack-ish solution , otherwise this can fire multiple times in a row, depending on animation length , speed , etc
let diff = frame as f32 - frame_seconds;
println!("diff {}", diff);
if diff < 0.1 {
for marker in matching_markers_per_frame {
animation_marker_events.send(AnimationMarkerReached {
entity,
animation_name: animation_name.clone(),
frame,
marker_name: marker.clone(),
});
}
}
}
}
}
break;
}
}
}
}
*/

View File

@ -1,4 +1,4 @@
use crate::{BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintInfo, BlueprintInstanceReady, BlueprintSpawning, SpawnBlueprint, SubBlueprintsSpawnTracker}; use crate::{BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintInfo, BlueprintInstanceReady, BlueprintSpawning, FromBlueprint, SpawnBlueprint, SubBlueprintsSpawnTracker};
use bevy::asset::AssetEvent; use bevy::asset::AssetEvent;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::scene::SceneInstance; use bevy::scene::SceneInstance;
@ -21,7 +21,7 @@ pub(crate) fn react_to_asset_changes(
&BlueprintInfo, &BlueprintInfo,
Option<&Children>, Option<&Children>,
)>, )>,
// blueprint_children_entities: Query<&InBlueprint>, => can only be used if the entites are tagged, right now that is optional...perhaps do not make it optional blueprint_children_entities: Query<&FromBlueprint>, //=> can only be used if the entites are tagged
assets_to_blueprint_instances: Res<AssetToBlueprintInstancesMapper>, assets_to_blueprint_instances: Res<AssetToBlueprintInstancesMapper>,
all_parents: Query<&Parent>, all_parents: Query<&Parent>,
spawning_blueprints: Query<&BlueprintSpawning>, spawning_blueprints: Query<&BlueprintSpawning>,

View File

@ -136,13 +136,15 @@ impl Plugin for BlueprintsPlugin {
.chain() .chain()
.in_set(GltfBlueprintsSet::Spawn), .in_set(GltfBlueprintsSet::Spawn),
) )
/* .add_systems(
// animation
.add_systems(
Update, Update,
( (
trigger_instance_animation_markers_events,
trigger_blueprint_animation_markers_events, trigger_blueprint_animation_markers_events,
trigger_instance_animation_markers_events
), ),
)*/ )
// hot reload // hot reload
.add_systems(Update, react_to_asset_changes.run_if(hot_reload)); .add_systems(Update, react_to_asset_changes.run_if(hot_reload));
} }

View File

@ -4,7 +4,7 @@ use bevy::{gltf::Gltf, prelude::*, scene::SceneInstance, utils::hashbrown::HashM
use serde_json::Value; use serde_json::Value;
use crate::{ use crate::{
AnimationInfos, AssetLoadTracker, AssetToBlueprintInstancesMapper, BlenvyConfig, BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintAssets, BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintAssetsNotLoaded, SceneAnimationPlayerLink, SceneAnimations AnimationInfos, AssetLoadTracker, AssetToBlueprintInstancesMapper, BlenvyConfig, BlueprintAnimationInfosLink, BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintAssets, BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintAssetsNotLoaded, SceneAnimationInfosLink, SceneAnimationPlayerLink, SceneAnimations
}; };
/// this is a flag component for our levels/game world /// this is a flag component for our levels/game world
@ -40,7 +40,7 @@ pub struct SpawnBlueprint;
#[derive(Component, Reflect, Default, Debug)] #[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)] #[reflect(Component)]
/// flag component marking any spwaned child of blueprints /// flag component marking any spwaned child of blueprints
pub struct InBlueprint; pub struct FromBlueprint;
#[derive(Component, Reflect, Default, Debug)] #[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)] #[reflect(Component)]
@ -570,10 +570,10 @@ pub(crate) fn blueprints_cleanup_spawned_scene(
} }
} }
// we flag all children of the blueprint instance with 'InBlueprint' // we flag all children of the blueprint instance with 'FromBlueprint'
// can be usefull to filter out anything that came from blueprints vs normal children // can be usefull to filter out anything that came from blueprints vs normal children
for child in all_children.iter_descendants(blueprint_root_entity) { for child in all_children.iter_descendants(blueprint_root_entity) {
commands.entity(child).insert(InBlueprint); // we do this here in order to avoid doing it to normal children commands.entity(child).insert(FromBlueprint); // we do this here in order to avoid doing it to normal children
} }
@ -626,9 +626,14 @@ pub(crate) fn blueprints_cleanup_spawned_scene(
all_names.get(child), all_names.get(child),
all_names.get(original) all_names.get(original)
); );
/*commands commands
.entity(original) .entity(original)
.insert((BlueprintAnimationPlayerLink(bla),)); */ .insert((
//BlueprintAnimationPlayerLink(bla),
BlueprintAnimationInfosLink(child)
))
;
} else { } else {
for parent in all_parents.iter_ancestors(child) { for parent in all_parents.iter_ancestors(child) {
if animation_players.get(parent).is_ok() { if animation_players.get(parent).is_ok() {
@ -651,6 +656,10 @@ pub(crate) fn blueprints_cleanup_spawned_scene(
}, },
)); ));
} }
if with_animation_infos.get(parent).is_ok() {
commands.entity(child).insert(SceneAnimationInfosLink(parent));
}
} }
} }
} }

View File

@ -1 +0,0 @@
({})

View File

@ -1,12 +0,0 @@
({
"world":File (path: "models/StartLevel.glb"),
"level1":File (path: "models/Level1.glb"),
"level2":File (path: "models/Level2.glb"),
"models": Folder (
path: "models/library",
),
"materials": Folder (
path: "materials",
),
})