feat(Blenvy:Bevy): added back basics of animation support

* updated logic to account for bevy 0.14 changes (animation graph, transitions component etc)
 * added back & updated per blueprint animation example to the testing project
 * updated matching blend file with new style components
 * spawnHere => spawnBlueprint
 * added back tagging of Blueprint contents with "InBlueprint" / noInBlueprint
 * overall cleanup pass (wip)
This commit is contained in:
kaosat.dev 2024-07-08 12:55:25 +02:00
parent 3099f98532
commit c326a11243
17 changed files with 210 additions and 123 deletions

View File

@ -52,7 +52,7 @@ fn spawn_blueprint(
if keycode.just_pressed(KeyCode::S) { if keycode.just_pressed(KeyCode::S) {
let new_entity = commands.spawn(( let new_entity = commands.spawn((
BlueprintInfo(name: "Health_Pickup".to_string(), path:""), // mandatory !! BlueprintInfo(name: "Health_Pickup".to_string(), path:""), // mandatory !!
SpawnHere, // mandatory !! SpawnBlueprint, // mandatory !!
TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), // VERY important !! TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), // VERY important !!
// any other component you want to insert // any other component you want to insert
)); ));
@ -115,7 +115,7 @@ You can spawn entities from blueprints like this:
```rust no_run ```rust no_run
commands.spawn(( commands.spawn((
BlueprintInfo("Health_Pickup".to_string()), // mandatory !! BlueprintInfo("Health_Pickup".to_string()), // mandatory !!
SpawnHere, // mandatory !! SpawnBlueprint, // mandatory !!
TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), // optional TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), // optional
// any other component you want to insert // any other component you want to insert
@ -135,7 +135,7 @@ you can just add any additional components you need when spawning :
```rust no_run ```rust no_run
commands.spawn(( commands.spawn((
BlueprintInfo("Health_Pickup".to_string()), BlueprintInfo("Health_Pickup".to_string()),
SpawnHere, SpawnBlueprint,
TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)),
// from Rapier/bevy_xpbd: this means the entity will also have a velocity component when inserted into the world // from Rapier/bevy_xpbd: this means the entity will also have a velocity component when inserted into the world
Velocity { Velocity {
@ -153,7 +153,7 @@ for example
```rust no_run ```rust no_run
commands.spawn(( commands.spawn((
BlueprintInfo("Health_Pickup".to_string()), BlueprintInfo("Health_Pickup".to_string()),
SpawnHere, SpawnBlueprint,
TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)),
HealthPowerUp(20)// if this is component is also present inside the "Health_Pickup" blueprint, that one will be replaced with this component during spawning HealthPowerUp(20)// if this is component is also present inside the "Health_Pickup" blueprint, that one will be replaced with this component during spawning
)) ))
@ -164,7 +164,7 @@ commands.spawn((
There is also a ```BluePrintBundle``` for convenience , which just has There is also a ```BluePrintBundle``` for convenience , which just has
* a ```BlueprintInfo``` component * a ```BlueprintInfo``` component
* a ```SpawnHere``` component * a ```SpawnBlueprint``` component
## Additional information ## Additional information

View File

@ -49,7 +49,7 @@ pub(crate) fn prepare_blueprints(
Option<&Name>, Option<&Name>,
Option<&BlueprintsList>, Option<&BlueprintsList>,
), ),
(Added<BlueprintName>, Added<SpawnHere>, Without<Spawned>), (Added<BlueprintName>, Added<SpawnBlueprint>, Without<Spawned>),
>, >,
mut commands: Commands, mut commands: Commands,

View File

@ -22,7 +22,7 @@ pub struct BlueprintPath(pub String);
/// flag component needed to signify the intent to spawn a Blueprint /// flag component needed to signify the intent to spawn a Blueprint
#[derive(Component, Reflect, Default, Debug)] #[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)] #[reflect(Component)]
pub struct SpawnHere; pub struct SpawnBlueprint;
#[derive(Component)] #[derive(Component)]
/// flag component for dynamically spawned scenes /// flag component for dynamically spawned scenes
@ -93,7 +93,7 @@ pub(crate) fn blueprints_prepare_spawn(
Entity, Entity,
&BlueprintPath, &BlueprintPath,
), ),
(Added<BlueprintPath>, Without<Spawned>, Without<SpawnHere>)>, (Added<BlueprintPath>, Without<Spawned>, Without<SpawnBlueprint>)>,
// before 0.14 we have to use a seperate query, after migrating we can query at the root level // before 0.14 we have to use a seperate query, after migrating we can query at the root level
entities_with_assets: Query< entities_with_assets: Query<

View File

@ -6,7 +6,7 @@ use bevy::scene::SceneInstance;
// use bevy::utils::hashbrown::HashSet; // use bevy::utils::hashbrown::HashSet;
use crate::{BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintInfo, BlueprintReadyForPostProcess, BlueprintInstanceReady, BlueprintSpawning, SubBlueprintSpawnRoot, SubBlueprintsSpawnTracker}; use crate::{BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintInfo, BlueprintReadyForPostProcess, BlueprintInstanceReady, BlueprintSpawning, SubBlueprintSpawnRoot, SubBlueprintsSpawnTracker};
use crate::{SpawnHere, Spawned}; use crate::{SpawnBlueprint, Spawned};
use crate::{ use crate::{
BlueprintEvent, CopyComponents, InBlueprint, NoInBlueprint, OriginalChildren BlueprintEvent, CopyComponents, InBlueprint, NoInBlueprint, OriginalChildren
}; };
@ -34,7 +34,7 @@ pub(crate) fn spawned_blueprint_post_process( // rename to '
// sub blueprint instances tracker // sub blueprint instances tracker
Option<&SubBlueprintSpawnRoot> Option<&SubBlueprintSpawnRoot>
), ),
(With<SpawnHere>, With<SceneInstance>, Added<BlueprintReadyForPostProcess>), (With<SpawnBlueprint>, With<SceneInstance>, Added<BlueprintReadyForPostProcess>),
>, >,
added_animation_players: Query<(Entity, &Parent), Added<AnimationPlayer>>, added_animation_players: Query<(Entity, &Parent), Added<AnimationPlayer>>,
all_children: Query<&Children>, all_children: Query<&Children>,
@ -103,7 +103,7 @@ pub(crate) fn spawned_blueprint_post_process( // rename to '
} }
} }
commands.entity(original).remove::<SpawnHere>(); commands.entity(original).remove::<SpawnBlueprint>();
commands.entity(original).remove::<Spawned>(); commands.entity(original).remove::<Spawned>();
// commands.entity(original).remove::<Handle<Scene>>(); // FIXME: if we delete the handle to the scene, things get despawned ! not what we want // commands.entity(original).remove::<Handle<Scene>>(); // FIXME: if we delete the handle to the scene, things get despawned ! not what we want
//commands.entity(original).remove::<BlueprintAssetsLoadState>(); // also clear the sub assets tracker to free up handles, perhaps just freeing up the handles and leave the rest would be better ? //commands.entity(original).remove::<BlueprintAssetsLoadState>(); // also clear the sub assets tracker to free up handles, perhaps just freeing up the handles and leave the rest would be better ?

View File

@ -6,6 +6,8 @@ use bevy::utils::HashMap;
/// storage for animations for a given entity's BLUEPRINT (ie for example a characters animations), essentially a clone of gltf's `named_animations` /// storage for animations for a given entity's BLUEPRINT (ie for example a characters animations), essentially a clone of gltf's `named_animations`
pub struct BlueprintAnimations { pub struct BlueprintAnimations {
pub named_animations: HashMap<String, Handle<AnimationClip>>, pub named_animations: HashMap<String, Handle<AnimationClip>>,
pub named_indices: HashMap<String, AnimationNodeIndex>,
pub graph: Handle<AnimationGraph>
} }
#[derive(Component, Debug)] #[derive(Component, Debug)]
@ -20,6 +22,7 @@ pub struct BlueprintAnimationPlayerLink(pub Entity);
/// 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`
pub struct SceneAnimations { pub struct SceneAnimations {
pub named_animations: HashMap<String, Handle<AnimationClip>>, pub named_animations: HashMap<String, Handle<AnimationClip>>,
pub named_indices: HashMap<String, AnimationNodeIndex>
} }
#[derive(Component, Debug)] #[derive(Component, Debug)]

View File

@ -2,7 +2,7 @@
use bevy::prelude::*; use bevy::prelude::*;
use bevy::asset::{AssetEvent, LoadedUntypedAsset}; use bevy::asset::{AssetEvent, LoadedUntypedAsset};
use bevy::scene::SceneInstance; use bevy::scene::SceneInstance;
use crate::{BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintInfo, SpawnHere}; use crate::{BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintInfo, SpawnBlueprint};
pub(crate) fn react_to_asset_changes( pub(crate) fn react_to_asset_changes(
mut gltf_events: EventReader<AssetEvent<Gltf>>, mut gltf_events: EventReader<AssetEvent<Gltf>>,
@ -33,7 +33,7 @@ pub(crate) fn react_to_asset_changes(
.remove::<BlueprintAssetsLoaded>() .remove::<BlueprintAssetsLoaded>()
.remove::<SceneInstance>() .remove::<SceneInstance>()
.remove::<BlueprintAssetsLoadState>() .remove::<BlueprintAssetsLoadState>()
.insert(SpawnHere); .insert(SpawnBlueprint);
break; break;
} }

View File

@ -36,13 +36,13 @@ pub enum GltfBlueprintsSet {
#[derive(Bundle)] #[derive(Bundle)]
pub struct BluePrintBundle { pub struct BluePrintBundle {
pub blueprint: BlueprintInfo, pub blueprint: BlueprintInfo,
pub spawn_here: SpawnHere, pub spawn_here: SpawnBlueprint,
} }
impl Default for BluePrintBundle { impl Default for BluePrintBundle {
fn default() -> Self { fn default() -> Self {
BluePrintBundle { BluePrintBundle {
blueprint: BlueprintInfo{ name: "default".into(), path:"".into()}, blueprint: BlueprintInfo{ name: "default".into(), path:"".into()},
spawn_here: SpawnHere, spawn_here: SpawnBlueprint,
} }
} }
} }
@ -105,7 +105,7 @@ impl Plugin for BlueprintsPlugin {
.register_type::<BlueprintInfo>() .register_type::<BlueprintInfo>()
.register_type::<MaterialInfo>() .register_type::<MaterialInfo>()
.register_type::<SpawnHere>() .register_type::<SpawnBlueprint>()
.register_type::<BlueprintAnimations>() .register_type::<BlueprintAnimations>()
.register_type::<SceneAnimations>() .register_type::<SceneAnimations>()
.register_type::<AnimationInfo>() .register_type::<AnimationInfo>()
@ -136,7 +136,7 @@ impl Plugin for BlueprintsPlugin {
blueprints_check_assets_loading, blueprints_check_assets_loading,
blueprints_assets_ready, blueprints_assets_ready,
blueprints_scenes_spawned, blueprints_scenes_spawned,
blueprints_transfer_components, blueprints_cleanup_spawned_scene,
// post process // post process
inject_materials, inject_materials,

View File

@ -1,9 +1,9 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use bevy::{gltf::Gltf, prelude::*, render::view::visibility, scene::SceneInstance, transform::commands, utils::hashbrown::HashMap}; use bevy::{gltf::Gltf, prelude::*, scene::SceneInstance, utils::hashbrown::HashMap};
use serde_json::Value; use serde_json::Value;
use crate::{BlueprintAssets, BlueprintAssetsLoadState, AssetLoadTracker, BlenvyConfig, BlueprintAnimations, BlueprintAssetsLoaded, BlueprintAssetsNotLoaded}; use crate::{AssetLoadTracker, BlenvyConfig, BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintAssets, BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintAssetsNotLoaded};
/// this is a flag component for our levels/game world /// this is a flag component for our levels/game world
#[derive(Component)] #[derive(Component)]
@ -22,12 +22,7 @@ pub struct BlueprintInfo {
/// flag component needed to signify the intent to spawn a Blueprint /// flag component needed to signify the intent to spawn a Blueprint
#[derive(Component, Reflect, Default, Debug)] #[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)] #[reflect(Component)]
pub struct SpawnHere; pub struct SpawnBlueprint;
#[derive(Component)]
/// flag component for dynamically spawned scenes
pub struct Spawned;
#[derive(Component, Debug)] #[derive(Component, Debug)]
/// flag component added when a Blueprint instance ist Ready : ie : /// flag component added when a Blueprint instance ist Ready : ie :
@ -103,12 +98,28 @@ pub struct BlueprintSpawning;
use gltf::Gltf as RawGltf; use gltf::Gltf as RawGltf;
/*
Overview of the Blueprint Spawning process
- Blueprint Load Assets
- Blueprint Assets Ready: spawn Blueprint's scene
- Blueprint Scene Ready (SceneInstance component is present):
- get list of sub Blueprints if any, inject sub blueprints spawn tracker
- Blueprint copy components to original entity, remove useless nodes
- Blueprint post process
- generate aabb (need full hierarchy in its final form)
- inject materials from library if needed
- Blueprint Ready
- bubble information up to parent blueprint instance
- if all sub_blueprints are ready => Parent blueprint Instance is ready
=> distinguish between blueprint instances inside blueprint instances vs blueprint instances inside blueprints ??
*/
pub(crate) fn blueprints_prepare_spawn( pub(crate) fn blueprints_prepare_spawn(
blueprint_instances_to_spawn : Query< blueprint_instances_to_spawn : Query<
( (
Entity, Entity,
&BlueprintInfo, &BlueprintInfo,
),(Added<SpawnHere>) ),(Added<SpawnBlueprint>)
>, >,
mut commands: Commands, mut commands: Commands,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
@ -118,7 +129,6 @@ asset_server: Res<AssetServer>,
info!("BLUEPRINT: to spawn detected: {:?} path:{:?}", blueprint_info.name, blueprint_info.path); info!("BLUEPRINT: to spawn detected: {:?} path:{:?}", blueprint_info.name, blueprint_info.path);
//println!("all assets {:?}", all_assets); //println!("all assets {:?}", all_assets);
////////////// //////////////
// we add the asset of the blueprint itself // we add the asset of the blueprint itself
// TODO: add detection of already loaded data // TODO: add detection of already loaded data
let untyped_handle = asset_server.load_untyped(&blueprint_info.path); let untyped_handle = asset_server.load_untyped(&blueprint_info.path);
@ -140,12 +150,8 @@ asset_server: Res<AssetServer>,
/* prefetch attempt */ /* prefetch attempt */
let gltf = RawGltf::open(format!("assets/{}", blueprint_info.path)).unwrap(); let gltf = RawGltf::open(format!("assets/{}", blueprint_info.path)).unwrap();
for scene in gltf.scenes() { for scene in gltf.scenes() {
let foo_extras = scene.extras().clone().unwrap(); let scene_extras = scene.extras().clone().unwrap();
let lookup: HashMap<String, Value> = serde_json::from_str(&scene_extras.get()).unwrap();
let lookup: HashMap<String, Value> = serde_json::from_str(&foo_extras.get()).unwrap();
/*for (key, value) in lookup.clone().into_iter() {
println!("{} / {}", key, value);
}*/
if lookup.contains_key("BlueprintAssets"){ if lookup.contains_key("BlueprintAssets"){
let assets_raw = &lookup["BlueprintAssets"]; let assets_raw = &lookup["BlueprintAssets"];
@ -155,12 +161,8 @@ asset_server: Res<AssetServer>,
for asset in all_assets.assets.iter() { for asset in all_assets.assets.iter() {
let untyped_handle = asset_server.load_untyped(&asset.path); let untyped_handle = asset_server.load_untyped(&asset.path);
//println!("untyped handle {:?}", untyped_handle);
//asset_server.load(asset.path);
let asset_id = untyped_handle.id(); let asset_id = untyped_handle.id();
//println!("ID {:?}", asset_id);
let loaded = asset_server.is_loaded_with_dependencies(asset_id); let loaded = asset_server.is_loaded_with_dependencies(asset_id);
//println!("Loaded ? {:?}", loaded);
if !loaded { if !loaded {
asset_infos.push(AssetLoadTracker { asset_infos.push(AssetLoadTracker {
name: asset.name.clone(), name: asset.name.clone(),
@ -175,25 +177,28 @@ asset_server: Res<AssetServer>,
} }
// now insert load tracker // now insert load tracker
// if there are assets to load
if !asset_infos.is_empty() { if !asset_infos.is_empty() {
commands commands
.entity(entity) .entity(entity)
.insert(BlueprintAssetsLoadState { .insert((
all_loaded: false, BlueprintAssetsLoadState {
asset_infos, all_loaded: false,
..Default::default() asset_infos,
}) ..Default::default()
.insert(BlueprintAssetsNotLoaded) },
BlueprintAssetsNotLoaded
))
; ;
} else { } else {
commands.entity(entity).insert(BlueprintAssetsLoaded); commands.entity(entity).insert(BlueprintAssetsLoaded);
} }
// add the blueprint spawning marker
commands.entity(entity).insert(BlueprintSpawning); commands.entity(entity).insert(BlueprintSpawning);
} }
} }
/// This system tracks & updates the loading state of all blueprints assets
pub(crate) fn blueprints_check_assets_loading( pub(crate) fn blueprints_check_assets_loading(
mut blueprint_assets_to_load: Query< mut blueprint_assets_to_load: Query<
(Entity, &BlueprintInfo, &mut BlueprintAssetsLoadState), (Entity, &BlueprintInfo, &mut BlueprintAssetsLoadState),
@ -211,7 +216,6 @@ pub(crate) fn blueprints_check_assets_loading(
for tracker in assets_to_load.asset_infos.iter_mut() { for tracker in assets_to_load.asset_infos.iter_mut() {
let asset_id = tracker.id; let asset_id = tracker.id;
let loaded = asset_server.is_loaded_with_dependencies(asset_id); let loaded = asset_server.is_loaded_with_dependencies(asset_id);
// println!("loading {}: // load state: {:?}", tracker.name, asset_server.load_state(asset_id));
// FIXME: hack for now // FIXME: hack for now
let mut failed = false;// asset_server.load_state(asset_id) == bevy::asset::LoadState::Failed(_error); let mut failed = false;// asset_server.load_state(asset_id) == bevy::asset::LoadState::Failed(_error);
@ -230,6 +234,7 @@ pub(crate) fn blueprints_check_assets_loading(
} }
let progress: f32 = loaded_amount as f32 / total as f32; let progress: f32 = loaded_amount as f32 / total as f32;
assets_to_load.progress = progress; assets_to_load.progress = progress;
// println!("LOADING: in progress for ALL assets of {:?} (instance of {}): {} ",entity_name, blueprint_info.path, progress * 100.0);
if all_loaded { if all_loaded {
assets_to_load.all_loaded = true; assets_to_load.all_loaded = true;
@ -240,10 +245,8 @@ pub(crate) fn blueprints_check_assets_loading(
.entity(entity) .entity(entity)
.insert(BlueprintAssetsLoaded) .insert(BlueprintAssetsLoaded)
.remove::<BlueprintAssetsNotLoaded>() .remove::<BlueprintAssetsNotLoaded>()
//.remove::<BlueprintAssetsLoadState>() //REMOVE it in release mode/ when hot reload is off, keep it for dev/hot reload //.remove::<BlueprintAssetsLoadState>() //REMOVE this component in release mode/ when hot reload is off, keep it for dev/hot reload
; ;
}else {
// println!("LOADING: in progress for ALL assets of {:?} (instance of {}): {} ",entity_name, blueprint_info.path, progress * 100.0);
} }
} }
} }
@ -265,13 +268,15 @@ pub(crate) fn blueprints_assets_ready(spawn_placeholders: Query<
Without<BlueprintAssetsNotLoaded>, Without<BlueprintAssetsNotLoaded>,
), ),
>, >,
all_children: Query<&Children>,
mut commands: Commands,
mut game_world: Query<Entity, With<GameWorldTag>>, mut game_world: Query<Entity, With<GameWorldTag>>,
assets_gltf: Res<Assets<Gltf>>, assets_gltf: Res<Assets<Gltf>>,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
children: Query<&Children>,)
mut graphs: ResMut<Assets<AnimationGraph>>,
mut commands: Commands,
)
{ {
for ( for (
entity, entity,
@ -319,17 +324,24 @@ pub(crate) fn blueprints_assets_ready(spawn_placeholders: Query<
} }
let mut original_children: Vec<Entity> = vec![]; let mut original_children: Vec<Entity> = vec![];
if let Ok(c) = children.get(entity) { if let Ok(c) = all_children.get(entity) {
for child in c.iter() { for child in c.iter() {
original_children.push(*child); original_children.push(*child);
} }
} }
// TODO: not a fan of this // TODO: not a fan of this
// prepare data for animations
let mut graph = AnimationGraph::new();
let mut named_animations:HashMap<String, Handle<AnimationClip>> = HashMap::new() ; let mut named_animations:HashMap<String, Handle<AnimationClip>> = HashMap::new() ;
for (key, value) in gltf.named_animations.iter() { let mut animation_indices:HashMap<String, AnimationNodeIndex> = HashMap::new();
named_animations.insert(key.to_string(), value.clone());
for (key, clip) in gltf.named_animations.iter() {
named_animations.insert(key.to_string(), clip.clone());
let animation_index = graph.add_clip(clip.clone(), 1.0, graph.root);
animation_indices.insert(key.to_string(), animation_index);
} }
let graph = graphs.add(graph);
commands.entity(entity).insert(( commands.entity(entity).insert((
SceneBundle { SceneBundle {
@ -338,9 +350,12 @@ pub(crate) fn blueprints_assets_ready(spawn_placeholders: Query<
..Default::default() ..Default::default()
}, },
OriginalChildren(original_children), OriginalChildren(original_children),
BlueprintAnimations { BlueprintAnimations {
// these are animations specific to the inside of the blueprint // these are animations specific to the inside of the blueprint
named_animations: named_animations//gltf.named_animations.clone(), named_animations: named_animations, //gltf.named_animations.clone(),
named_indices: animation_indices,
graph
}, },
)); ));
@ -358,7 +373,6 @@ pub(crate) fn blueprints_assets_ready(spawn_placeholders: Query<
commands.entity(world).add_child(entity); commands.entity(world).add_child(entity);
} }
} }
} }
} }
@ -399,16 +413,13 @@ pub(crate) fn blueprints_scenes_spawned(
info!("Done spawning blueprint scene for entity named {:?} (track root: {:?})", name, track_root); info!("Done spawning blueprint scene for entity named {:?} (track root: {:?})", name, track_root);
let mut sub_blueprint_instances: Vec<Entity> = vec![]; let mut sub_blueprint_instances: Vec<Entity> = vec![];
let mut sub_blueprint_instance_names: Vec<Name> = vec![]; let mut sub_blueprint_instance_names: Vec<Name> = vec![];
let mut tracker_data: HashMap<Entity, bool> = HashMap::new(); let mut tracker_data: HashMap<Entity, bool> = HashMap::new();
if track_root.is_none() { if track_root.is_none() {
for parent in all_parents.iter_ancestors(entity) { for parent in all_parents.iter_ancestors(entity) {
if with_blueprint_infos.get(parent).is_ok() { if with_blueprint_infos.get(parent).is_ok() {
println!("found a parent with blueprint_info {:?} for {:?}", all_names.get(parent), all_names.get(entity)); println!("found a parent with blueprint_info {:?} for {:?}", all_names.get(parent), all_names.get(entity));
commands.entity(entity).insert(SubBlueprintSpawnRoot(parent));// Injecting to know which entity is the root commands.entity(entity).insert(SubBlueprintSpawnRoot(parent));// Injecting to know which entity is the root
break; break;
} }
} }
@ -451,9 +462,9 @@ pub(crate) fn blueprints_scenes_spawned(
println!("sub blueprint instances {:?}", sub_blueprint_instance_names); println!("sub blueprint instances {:?}", sub_blueprint_instance_names);
// TODO: how about when no sub blueprints are present
if tracker_data.keys().len() > 0 { if tracker_data.keys().len() > 0 {
commands.entity(entity) commands
.entity(entity)
.insert(SubBlueprintsSpawnTracker{sub_blueprint_instances: tracker_data.clone()}); .insert(SubBlueprintsSpawnTracker{sub_blueprint_instances: tracker_data.clone()});
}else { }else {
commands.entity(entity).insert(BlueprintChildrenReady); commands.entity(entity).insert(BlueprintChildrenReady);
@ -475,16 +486,21 @@ pub struct BlueprintReadyForPostProcess;
/// - it removes one level of useless nesting /// - it removes one level of useless nesting
/// - it copies the blueprint's root components to the entity it was spawned on (original entity) /// - it copies the blueprint's root components to the entity it was spawned on (original entity)
/// - it copies the children of the blueprint scene into the original entity /// - it copies the children of the blueprint scene into the original entity
/// - it add `AnimationLink` components so that animations can be controlled from the original entity /// - it adds an `AnimationLink` component containing the entity that has the AnimationPlayer so that animations can be controlled from the original entity
pub(crate) fn blueprints_transfer_components( pub(crate) fn blueprints_cleanup_spawned_scene(
foo: Query<( foo: Query<(
Entity, Entity,
&Children, &Children,
&OriginalChildren, &OriginalChildren,
Option<&Name>, Option<&Name>,
Option<&SubBlueprintSpawnRoot>), Option<&SubBlueprintSpawnRoot>,
&BlueprintAnimations,
Option<&NoInBlueprint>
),
Added<BlueprintChildrenReady> Added<BlueprintChildrenReady>
>, >,
added_animation_players: Query<(Entity, &Parent), Added<AnimationPlayer>>,
mut sub_blueprint_trackers: Query<(Entity, &mut SubBlueprintsSpawnTracker, &BlueprintInfo)>, mut sub_blueprint_trackers: Query<(Entity, &mut SubBlueprintsSpawnTracker, &BlueprintInfo)>,
all_children: Query<&Children>, all_children: Query<&Children>,
@ -493,7 +509,7 @@ pub(crate) fn blueprints_transfer_components(
all_names: Query<&Name> all_names: Query<&Name>
) { ) {
for (original, children, original_children, name, track_root) in foo.iter() { for (original, children, original_children, name, track_root, animations, no_inblueprint) in foo.iter() {
info!("YOOO ready !! removing empty nodes {:?}", name); info!("YOOO ready !! removing empty nodes {:?}", name);
if children.len() == 0 { if children.len() == 0 {
@ -511,6 +527,14 @@ pub(crate) fn blueprints_transfer_components(
} }
} }
// we flag all children of the blueprint instance with 'InBlueprint'
// can be usefull to filter out anything that came from blueprints vs normal children
if no_inblueprint.is_none() {
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
}
}
// copy components into from blueprint instance's blueprint_root_entity to original entity // copy components into from blueprint instance's blueprint_root_entity to original entity
commands.add(CopyComponents { commands.add(CopyComponents {
source: blueprint_root_entity, source: blueprint_root_entity,
@ -527,6 +551,31 @@ pub(crate) fn blueprints_transfer_components(
} }
} }
if animations.named_animations.keys().len() > 0 {
for (added, parent) in added_animation_players.iter() {
if parent.get() == blueprint_root_entity {
// FIXME: stopgap solution: since we cannot use an AnimationPlayer at the root entity level
// and we cannot update animation clips so that the EntityPaths point to one level deeper,
// BUT we still want to have some marker/control at the root entity level, we add this
commands
.entity(original)
.insert((
BlueprintAnimationPlayerLink(added),
));
// since v0.14 you need both AnimationTransitions and AnimationGraph components/handle on the same entity as the animationPlayer
let transitions = AnimationTransitions::new();
commands
.entity(added)
.insert((
transitions,
animations.graph.clone()
));
}
}
}
commands.entity(original) commands.entity(original)
.insert(BlueprintReadyForPostProcess); // Tag the entity so any systems dealing with post processing can know it is now their "turn" .insert(BlueprintReadyForPostProcess); // Tag the entity so any systems dealing with post processing can know it is now their "turn"
@ -574,7 +623,7 @@ pub(crate) fn blueprints_finalize_instances(
for (entity, name, blueprint_info, hide_until_ready) in blueprint_instances.iter() { for (entity, name, blueprint_info, hide_until_ready) in blueprint_instances.iter() {
info!("Finalizing blueprint instance {:?}", name); info!("Finalizing blueprint instance {:?}", name);
commands.entity(entity) commands.entity(entity)
.remove::<SpawnHere>() .remove::<SpawnBlueprint>()
.remove::<BlueprintSpawning>() .remove::<BlueprintSpawning>()
.remove::<BlueprintReadyForPostProcess>() .remove::<BlueprintReadyForPostProcess>()
// .remove::<Handle<Scene>>(); // FIXME: if we delete the handle to the scene, things get despawned ! not what we want // .remove::<Handle<Scene>>(); // FIXME: if we delete the handle to the scene, things get despawned ! not what we want
@ -588,24 +637,7 @@ pub(crate) fn blueprints_finalize_instances(
commands.entity(entity).insert(Visibility::Visible); commands.entity(entity).insert(Visibility::Visible);
} }
blueprint_events.send(BlueprintEvent::InstanceReady {entity: entity, blueprint_name: blueprint_info.name.clone(), blueprint_path: blueprint_info.path.clone()}); blueprint_events.send(BlueprintEvent::InstanceReady {entity: entity, blueprint_name: blueprint_info.name.clone(), blueprint_path: blueprint_info.path.clone()});
} }
} }
/*
=> annoying issue with the "nested" useless root node created by blender
=> distinguish between blueprint instances inside blueprint instances vs blueprint instances inside blueprints ??
BlueprintSpawning
- Blueprint Load Assets
- Blueprint Assets Ready: spawn Blueprint's scene
- Blueprint Scene Ready (SceneInstance component is present):
- get list of sub Blueprints if any, inject sub blueprints spawn tracker
- Blueprint copy components to original entity, remove useless nodes
- Blueprint post process
- generate aabb (need full hierarchy in its final form)
- inject materials from library if needed
- Blueprint Ready
- bubble information up to parent blueprint instance
- if all sub_blueprints are ready => Parent blueprint Instance is ready
*/

View File

@ -12003,6 +12003,24 @@
} }
} }
}, },
"bevy_utils::hashbrown::HashMap<alloc::string::String, petgraph::graph::NodeIndex, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>": {
"isComponent": false,
"isResource": false,
"keyType": {
"type": {
"$ref": "#/$defs/alloc::string::String"
}
},
"long_name": "bevy_utils::hashbrown::HashMap<alloc::string::String, petgraph::graph::NodeIndex, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>",
"short_name": "HashMap<String, NodeIndex, DefaultHashBuilder>",
"type": "object",
"typeInfo": "Map",
"valueType": {
"type": {
"$ref": "#/$defs/petgraph::graph::NodeIndex"
}
}
},
"bevy_utils::hashbrown::HashMap<bevy_animation::AnimationTargetId, alloc::vec::Vec<bevy_animation::VariableCurve>, bevy_utils::NoOpHash>": { "bevy_utils::hashbrown::HashMap<bevy_animation::AnimationTargetId, alloc::vec::Vec<bevy_animation::VariableCurve>, bevy_utils::NoOpHash>": {
"isComponent": false, "isComponent": false,
"isResource": false, "isResource": false,
@ -13286,14 +13304,26 @@
"isResource": false, "isResource": false,
"long_name": "blenvy::blueprints::animation::BlueprintAnimations", "long_name": "blenvy::blueprints::animation::BlueprintAnimations",
"properties": { "properties": {
"graph": {
"type": {
"$ref": "#/$defs/bevy_asset::handle::Handle<bevy_animation::graph::AnimationGraph>"
}
},
"named_animations": { "named_animations": {
"type": { "type": {
"$ref": "#/$defs/bevy_utils::hashbrown::HashMap<alloc::string::String, bevy_asset::handle::Handle<bevy_animation::AnimationClip>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>" "$ref": "#/$defs/bevy_utils::hashbrown::HashMap<alloc::string::String, bevy_asset::handle::Handle<bevy_animation::AnimationClip>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>"
} }
},
"named_indices": {
"type": {
"$ref": "#/$defs/bevy_utils::hashbrown::HashMap<alloc::string::String, petgraph::graph::NodeIndex, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>"
}
} }
}, },
"required": [ "required": [
"named_animations" "named_animations",
"named_indices",
"graph"
], ],
"short_name": "BlueprintAnimations", "short_name": "BlueprintAnimations",
"type": "object", "type": "object",
@ -13309,10 +13339,16 @@
"type": { "type": {
"$ref": "#/$defs/bevy_utils::hashbrown::HashMap<alloc::string::String, bevy_asset::handle::Handle<bevy_animation::AnimationClip>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>" "$ref": "#/$defs/bevy_utils::hashbrown::HashMap<alloc::string::String, bevy_asset::handle::Handle<bevy_animation::AnimationClip>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>"
} }
},
"named_indices": {
"type": {
"$ref": "#/$defs/bevy_utils::hashbrown::HashMap<alloc::string::String, petgraph::graph::NodeIndex, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>"
}
} }
}, },
"required": [ "required": [
"named_animations" "named_animations",
"named_indices"
], ],
"short_name": "SceneAnimations", "short_name": "SceneAnimations",
"type": "object", "type": "object",
@ -13435,14 +13471,14 @@
"type": "object", "type": "object",
"typeInfo": "Struct" "typeInfo": "Struct"
}, },
"blenvy::blueprints::spawn_from_blueprints::SpawnHere": { "blenvy::blueprints::spawn_from_blueprints::SpawnBlueprint": {
"additionalProperties": false, "additionalProperties": false,
"isComponent": true, "isComponent": true,
"isResource": false, "isResource": false,
"long_name": "blenvy::blueprints::spawn_from_blueprints::SpawnHere", "long_name": "blenvy::blueprints::spawn_from_blueprints::SpawnBlueprint",
"properties": {}, "properties": {},
"required": [], "required": [],
"short_name": "SpawnHere", "short_name": "SpawnBlueprint",
"type": "object", "type": "object",
"typeInfo": "Struct" "typeInfo": "Struct"
}, },

View File

@ -5,7 +5,7 @@ use std::time::Duration;
SceneAnimationPlayerLink, SceneAnimations, SceneAnimationPlayerLink, SceneAnimations,
};*/ };*/
use bevy::{gltf::Gltf, prelude::*}; use bevy::{animation::RepeatAnimation, gltf::Gltf, prelude::*};
use blenvy::{ use blenvy::{
AnimationInfos, AnimationMarkerReached, BlueprintAnimationPlayerLink, BlueprintAnimations, AnimationInfos, AnimationMarkerReached, BlueprintAnimationPlayerLink, BlueprintAnimations,
@ -79,11 +79,13 @@ pub fn animations(
} }
} }
} }
} }*/
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub fn play_animations( pub fn play_animations(
animated_marker1: Query< animated_fox: Query<(&BlueprintAnimationPlayerLink, &BlueprintAnimations), With<MarkerFox>>,
/*animated_marker1: Query<
(&SceneAnimationPlayerLink, &SceneAnimations), (&SceneAnimationPlayerLink, &SceneAnimations),
(With<AnimationInfos>, With<Marker1>), (With<AnimationInfos>, With<Marker1>),
>, >,
@ -99,32 +101,43 @@ pub fn play_animations(
&BlueprintAnimations, &BlueprintAnimations,
), ),
(With<AnimationInfos>, With<Marker3>), (With<AnimationInfos>, With<Marker3>),
>, >, */
animated_fox: Query<(&BlueprintAnimationPlayerLink, &BlueprintAnimations), With<MarkerFox>>, mut animation_players: Query<(&mut AnimationPlayer, &mut AnimationTransitions)>,
mut animation_players: Query<&mut AnimationPlayer>,
keycode: Res<ButtonInput<KeyCode>>, keycode: Res<ButtonInput<KeyCode>>,
) { ) {
if keycode.just_pressed(KeyCode::KeyP) { if keycode.just_pressed(KeyCode::KeyP) {
println!("playing fox animation requested");
for (link, animations) in animated_fox.iter() { for (link, animations) in animated_fox.iter() {
println!("animations {:?}", animations.named_animations); println!("animations {:?}", animations.named_animations);
let mut animation_player = animation_players.get_mut(link.0).unwrap(); println!("LINK target {}", link.0);
let anim_name = "Run"; let (mut animation_player, mut animation_transitions) = animation_players.get_mut(link.0).unwrap();
animation_player let anim_name = "Survey";
.play_with_transition( let animation_index = animations
animations .named_indices
.named_animations .get(anim_name)
.get(anim_name) .expect("animation name should be in the list")
.expect("animation name should be in the list") .clone();
.clone(),
Duration::from_secs(5), animation_transitions
.play(
&mut animation_player,
animation_index,
Duration::from_secs(5)
) )
.repeat(); .repeat();
/*let Some((&playing_animation_index, _)) = animation_player.playing_animations().next() else {
continue;
};
let playing_animation = animation_player.animation_mut(playing_animation_index).unwrap();
println!("Playing animation {:?}", playing_animation);
playing_animation.set_repeat(RepeatAnimation::Forever);*/
} }
} }
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);
let mut animation_player = animation_players.get_mut(link.0).unwrap(); let mut animation_player = animation_players.get_mut(link.0).unwrap();
@ -229,9 +242,12 @@ pub fn play_animations(
) )
.repeat(); .repeat();
} }
} }*/
} }
*/
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

@ -1,5 +1,5 @@
use bevy::prelude::*; use bevy::prelude::*;
use blenvy::{AddToGameWorld, BluePrintBundle, BlueprintInfo, DynamicBlueprintInstance, GameWorldTag, HideUntilReady, SpawnHere}; use blenvy::{AddToGameWorld, BluePrintBundle, BlueprintInfo, DynamicBlueprintInstance, GameWorldTag, HideUntilReady, SpawnBlueprint};
use crate::{GameState, InAppRunning}; use crate::{GameState, InAppRunning};
//use bevy_rapier3d::prelude::Velocity; //use bevy_rapier3d::prelude::Velocity;
@ -25,7 +25,7 @@ pub fn setup_game(
BlueprintInfo{name: "World".into(), path: "levels/World.glb".into()}, BlueprintInfo{name: "World".into(), path: "levels/World.glb".into()},
HideUntilReady, HideUntilReady,
bevy::prelude::Name::from("world"), //FIXME: not really needed ? could be infered from blueprint's name/ path bevy::prelude::Name::from("world"), //FIXME: not really needed ? could be infered from blueprint's name/ path
SpawnHere, SpawnBlueprint,
GameWorldTag, GameWorldTag,
InAppRunning, InAppRunning,
)); ));
@ -43,7 +43,7 @@ pub fn spawn_test(
mut game_world: Query<(Entity, &Children), With<GameWorldTag>>, mut game_world: Query<(Entity, &Children), With<GameWorldTag>>,
) { ) {
if keycode.just_pressed(KeyCode::KeyT) { if keycode.just_pressed(KeyCode::KeyS) {
let world = game_world.single_mut(); let world = game_world.single_mut();
let world = world.1[0]; let world = world.1[0];

View File

@ -167,9 +167,9 @@ impl Plugin for GamePlugin {
/* .add_systems(Update, (animations) /* .add_systems(Update, (animations)
.run_if(in_state(AppState::AppRunning)) .run_if(in_state(AppState::AppRunning))
.after(GltfBlueprintsSet::AfterSpawn) .after(GltfBlueprintsSet::AfterSpawn)
) )*/
.add_systems(Update, play_animations) .add_systems(Update, play_animations)
.add_systems(Update, react_to_animation_markers)*/ //.add_systems(Update, react_to_animation_markers)
/*.add_systems(Update, generate_screenshot.run_if(on_timer(Duration::from_secs_f32(0.2)))) // TODO: run once /*.add_systems(Update, generate_screenshot.run_if(on_timer(Duration::from_secs_f32(0.2)))) // TODO: run once
.add_systems( .add_systems(

View File

@ -133,7 +133,7 @@ This issue has been resolved in v0.9.
You can enable this option to automatically replace all the **collection instances** inside your main scene with blueprints You can enable this option to automatically replace all the **collection instances** inside your main scene with blueprints
- whenever you change your main scene (or your library scene , if that option is enabled), all your collection instances - whenever you change your main scene (or your library scene , if that option is enabled), all your collection instances
* will be replaced with empties (this will not be visible to you) * will be replaced with empties (this will not be visible to you)
* those empties will have additional custom properties / components : ```BlueprintInfo``` & ```SpawnHere``` * those empties will have additional custom properties / components : ```BlueprintInfo``` & ```SpawnBlueprint```
* your main scene/ level will be exported to a much more trimmed down gltf file (see next point) * your main scene/ level will be exported to a much more trimmed down gltf file (see next point)
* all the original collections (that you used to create the instances) will be exported as **seperate gltf files** into the "library" folder * all the original collections (that you used to create the instances) will be exported as **seperate gltf files** into the "library" folder

View File

@ -218,7 +218,7 @@ Bevy Side:
- [x] make blueprint instances invisible until spawning is done to avoid "spawn flash"? - [x] make blueprint instances invisible until spawning is done to avoid "spawn flash"?
- [x] make this controlable via an additional "HideUntilReady" component - [x] make this controlable via an additional "HideUntilReady" component
- [x] register "HideUntilReady" so users can set this on their blueprints in Blender directly - [x] register "HideUntilReady" so users can set this on their blueprints in Blender directly
- [ ] restructure blueprint spawning - [x] restructure blueprint spawning
- [x] "blueprint ready" only be triggered after all its sub blueprints are ready - [x] "blueprint ready" only be triggered after all its sub blueprints are ready
- [x] "blueprintInstance ready"/finished - [x] "blueprintInstance ready"/finished
BlueprintAssetsLoaded BlueprintAssetsLoaded
@ -228,7 +228,7 @@ Bevy Side:
- [x] fix issues with deeply nested blueprints - [x] fix issues with deeply nested blueprints
- perhaps reverse logic by using iter_ascendants - perhaps reverse logic by using iter_ascendants
- [x] fix materials handling - [x] fix materials handling
- [ ] fix animations handling - [x] fix animations handling
- [ ] simplify testing example: - [ ] simplify testing example:
- [x] remove use of rapier physics (or even the whole common boilerplate ?) - [x] remove use of rapier physics (or even the whole common boilerplate ?)

View File

@ -98,7 +98,7 @@ def duplicate_object(object, parent, combine_mode, destination_collection, bluep
empty_obj = make_empty(original_name, object.location, object.rotation_euler, object.scale, destination_collection) empty_obj = make_empty(original_name, object.location, object.rotation_euler, object.scale, destination_collection)
"""we inject the collection/blueprint name & path, as a component called 'BlueprintInfo', but we only do this in the empty, not the original object""" """we inject the collection/blueprint name & path, as a component called 'BlueprintInfo', but we only do this in the empty, not the original object"""
empty_obj['SpawnHere'] = '()' empty_obj['SpawnBlueprint'] = '()'
empty_obj['BlueprintInfo'] = f'(name: "{blueprint_name}", path: "{blueprint_path}")' empty_obj['BlueprintInfo'] = f'(name: "{blueprint_name}", path: "{blueprint_path}")'
# we copy custom properties over from our original object to our empty # we copy custom properties over from our original object to our empty

View File

@ -89,7 +89,7 @@ expected_custom_property_values = {'bevy_animation::AnimationPlayer': '(animatio
'bevy_gltf_blueprints::animation::SceneAnimations': '(named_animations: "")', 'bevy_gltf_blueprints::animation::SceneAnimations': '(named_animations: "")',
'bevy_gltf_blueprints::materials::MaterialInfo': '(name: " ", source: " ")', 'bevy_gltf_blueprints::materials::MaterialInfo': '(name: " ", source: " ")',
'bevy_gltf_blueprints::spawn_from_blueprints::BlueprintsList': '({})', 'bevy_gltf_blueprints::spawn_from_blueprints::BlueprintsList': '({})',
'bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere': '()', 'bevy_gltf_blueprints::spawn_from_blueprints::SpawnBlueprint': '()',
'bevy_gltf_components::GltfProcessed': '()', 'bevy_gltf_components::GltfProcessed': '()',
'bevy_gltf_components::blender_settings::lighting::BlenderBackgroundShader': '(color: Rgba(red:1.0, green:1.0, ' 'bevy_gltf_components::blender_settings::lighting::BlenderBackgroundShader': '(color: Rgba(red:1.0, green:1.0, '
'blue:0.0, alpha:1.0), strength: 0.0)', 'blue:0.0, alpha:1.0), strength: 0.0)',
@ -347,7 +347,7 @@ expected_custom_property_values_randomized = {'bevy_animation::AnimationPlayer':
'bevy_gltf_blueprints::animation::SceneAnimations': '(named_animations: "")', 'bevy_gltf_blueprints::animation::SceneAnimations': '(named_animations: "")',
'bevy_gltf_blueprints::materials::MaterialInfo': '(name: "sbnpsago", source: "piuzfbqp")', 'bevy_gltf_blueprints::materials::MaterialInfo': '(name: "sbnpsago", source: "piuzfbqp")',
'bevy_gltf_blueprints::spawn_from_blueprints::BlueprintsList': '({})', 'bevy_gltf_blueprints::spawn_from_blueprints::BlueprintsList': '({})',
'bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere': '()', 'bevy_gltf_blueprints::spawn_from_blueprints::SpawnBlueprint': '()',
'bevy_gltf_components::GltfProcessed': '()', 'bevy_gltf_components::GltfProcessed': '()',
'bevy_gltf_components::blender_settings::lighting::BlenderBackgroundShader': '(color: Rgba(red:0.5714026093482971, ' 'bevy_gltf_components::blender_settings::lighting::BlenderBackgroundShader': '(color: Rgba(red:0.5714026093482971, '
'green:0.42888906598091125, ' 'green:0.42888906598091125, '