feat(Blenvy:Bevy):

* bumped up Bevy version to v0.14 !
 * fixed (albeit in a clunky way) issues with sub blueprint detection
 * improved error message for missing material library
 * added HideUntilReady component & logic, to hide 'in-spawning' blueprint instances until they are ready
 * "add-to-world" is now only trigerred for blueprint instances that have no parents (avoid footgun)
 * minor cleanups & tweaks
 * added test component with Vec3 to double check for issues
 * updated test blend file to include the component above + added a light to the test spawnable blueprint
to check for "light flashes" (aka indirectly testing "HideUntilReady")
This commit is contained in:
kaosat.dev 2024-07-07 22:17:44 +02:00
parent 478be88a55
commit bef709a0ed
10 changed files with 131 additions and 57 deletions

View File

@ -14,7 +14,7 @@ license = "MIT OR Apache-2.0"
workspace = true
[dependencies]
bevy = { version = "0.14.0-rc.3", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf", "file_watcher"] }
bevy = { version = "0.14", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf", "file_watcher"] }
serde = "1.0.188"
ron = "0.8.1"
serde_json = "1.0.108"
@ -34,4 +34,4 @@ gltf = { version = "1.4.0", default-features = false, features = [
[dev-dependencies]
bevy = { version = "0.14.0-rc.3", default-features = false, features = ["dynamic_linking"] }
bevy = { version = "0.14", default-features = false, features = ["dynamic_linking"] }

View File

@ -57,7 +57,7 @@ pub(crate) fn inject_materials(
let model_handle: Handle<Gltf> = asset_server.load(material_info.path.clone()); // FIXME: kinda weird now
let mat_gltf = assets_gltf
.get(model_handle.id())
.expect("material should have been preloaded");
.expect(&format!("materials file {} should have been preloaded", material_info.path));
if mat_gltf.named_materials.contains_key(&material_info.name as &str) {
let material = mat_gltf
.named_materials

View File

@ -60,6 +60,12 @@ pub struct AddToGameWorld;
pub(crate) struct OriginalChildren(pub Vec<Entity>);
#[derive(Component)]
/// You can add this component to a blueprint instance, and the instance will be hidden until it is ready
/// You usually want to use this for worlds/level spawning , or dynamic spawning at runtime, but not when you are adding blueprint instances to an existing entity
/// as it would first become invisible before re-appearing again
pub struct HideUntilReady;
#[derive(Event, Debug)]
pub enum BlueprintEvent {
@ -261,6 +267,7 @@ pub(crate) fn blueprints_assets_ready(spawn_placeholders: Query<
Option<&Parent>,
Option<&AddToGameWorld>,
Option<&Name>,
Option<&HideUntilReady>
),
(
With<BlueprintAssetsLoaded>,
@ -283,6 +290,7 @@ pub(crate) fn blueprints_assets_ready(spawn_placeholders: Query<
original_parent,
add_to_world,
name,
hide_until_ready,
) in spawn_placeholders.iter()
{
/*info!(
@ -336,8 +344,6 @@ pub(crate) fn blueprints_assets_ready(spawn_placeholders: Query<
SceneBundle {
scene: scene.clone(),
transform: transforms,
visibility: Visibility::Hidden,
..Default::default()
},
OriginalChildren(original_children),
@ -345,15 +351,19 @@ pub(crate) fn blueprints_assets_ready(spawn_placeholders: Query<
// these are animations specific to the inside of the blueprint
named_animations: named_animations//gltf.named_animations.clone(),
},
));
/* if add_to_world.is_some() {
if hide_until_ready.is_some() {
commands.entity(entity).insert(Visibility::Hidden); // visibility:
}
// only allow automatically adding a newly spawned blueprint instance to the "world", if the entity does not have a parent
if add_to_world.is_some() && original_parent.is_some() {
let world = game_world
.get_single_mut()
.expect("there should be a game world present");
commands.entity(world).add_child(entity);
} */
}
}
}
@ -382,8 +392,6 @@ pub struct BlueprintChildrenReady;
#[reflect(Component)]
pub struct BlueprintReadyForPostProcess;
// TODO: disregard blueprints that have been spawned WAIT , we already have BlueprintSpawning
pub(crate) fn blueprints_scenes_spawned(
spawned_blueprint_scene_instances: Query<(Entity, Option<&Name>, Option<&Children>, Option<&SpawnTrackRoot>), (With<BlueprintSpawning>, Added<SceneInstance>)>,
with_blueprint_infos : Query<(Entity, Option<&Name>), With<BlueprintInfo>>,
@ -396,7 +404,6 @@ pub(crate) fn blueprints_scenes_spawned(
mut commands: Commands,
all_names: Query<&Name>
) {
for (entity, name, children, track_root) in spawned_blueprint_scene_instances.iter(){
info!("Done spawning blueprint scene for entity named {:?} (track root: {:?})", name, track_root);
@ -405,34 +412,59 @@ pub(crate) fn blueprints_scenes_spawned(
let mut tracker_data: HashMap<Entity, bool> = HashMap::new();
for parent in all_parents.iter_ancestors(entity) {
if with_blueprint_infos.get(parent).is_ok() {
if track_root.is_none() {
for parent in all_parents.iter_ancestors(entity) {
if with_blueprint_infos.get(parent).is_ok() {
println!("found a parent with blueprint_info {:?} for {:?}", all_names.get(parent), all_names.get(entity));
commands.entity(entity).insert(SpawnTrackRoot(parent));// Injecting to know which entity is the root
println!("found a parent with blueprint_info {:?} for {:?}", all_names.get(parent), all_names.get(entity));
break;
break;
}
}
}
if children.is_some() {
for child in all_children.iter_descendants(entity) {
if with_blueprint_infos.get(child).is_ok() {
sub_blueprint_instances.push(child);
if let Ok(nname) = all_names.get(child) {
sub_blueprint_instance_names.push(nname.clone());
}
// println!("Parent blueprint instance of {:?} is {:?}", all_names.get(child), all_names.get(entity));
tracker_data.insert(child, false);
if track_root.is_some() {
let prev_root = track_root.unwrap().0;
// if we already had a track root, and it is different from the current entity , change the previous track root's list of children
if prev_root != entity {
let mut tracker = sub_blueprint_trackers.get_mut(prev_root).expect("should have a tracker");
tracker.1.sub_blueprint_instances.remove(&child);
for parent in all_parents.iter_ancestors(child) {
if with_blueprint_infos.get(parent).is_ok() {
if parent == entity {
//println!("yohoho");
println!("Parent blueprint instance of {:?} is {:?}", all_names.get(child), all_names.get(parent));
commands.entity(child).insert(SpawnTrackRoot(entity));// Injecting to know which entity is the root
tracker_data.insert(child, false);
sub_blueprint_instances.push(child);
if let Ok(nname) = all_names.get(child) {
sub_blueprint_instance_names.push(nname.clone());
}
/*if track_root.is_some() {
let prev_root = track_root.unwrap().0;
// if we already had a track root, and it is different from the current entity , change the previous track root's list of children
if prev_root != entity {
let mut tracker = sub_blueprint_trackers.get_mut(prev_root).expect("should have a tracker");
tracker.1.sub_blueprint_instances.remove(&child);
}
}*/
}
break;
}
}
commands.entity(child).insert(SpawnTrackRoot(entity));// Injecting to know which entity is the root
}
}
}
@ -481,7 +513,7 @@ pub(crate) fn blueprints_transfer_components(
) {
for (original, children, original_children, name, track_root) in foo.iter() {
println!("YOOO ready !! {:?}", name);
info!("YOOO ready !! removing empty nodes {:?}", name);
if children.len() == 0 {
warn!("timing issue ! no children found, please restart your bevy app (bug being investigated)");
@ -515,7 +547,7 @@ pub(crate) fn blueprints_transfer_components(
}
commands.entity(original)
.insert(BlueprintReadyForPostProcess); // Tag the entity so any systems dealing with post processing can now it is now their "turn"
.insert(BlueprintReadyForPostProcess); // Tag the entity so any systems dealing with post processing can know it is now their "turn"
// 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::<BlueprintAssetsLoaded>();
@ -524,6 +556,7 @@ pub(crate) fn blueprints_transfer_components(
// now check if the current entity is a child blueprint instance of another entity
// this should always be done last, as children should be finished before the parent can be processed correctly
// TODO: perhaps use observers for these
if let Some(track_root) = track_root {
let root_name = all_names.get(track_root.0);
println!("got some root {:?}", root_name);
@ -555,19 +588,23 @@ pub(crate) fn blueprints_transfer_components(
pub struct BlueprintReadyForFinalizing;
pub(crate) fn blueprints_finalize_instances(
blueprint_instances: Query<(Entity, Option<&Name>, &BlueprintInfo), (With<BlueprintSpawning>, With<BlueprintReadyForFinalizing>)>,
blueprint_instances: Query<(Entity, Option<&Name>, &BlueprintInfo, Option<&HideUntilReady>), (With<BlueprintSpawning>, With<BlueprintReadyForFinalizing>)>,
mut blueprint_events: EventWriter<BlueprintEvent>,
mut commands: Commands,
) {
for (entity, name, blueprint_info) in blueprint_instances.iter() {
for (entity, name, blueprint_info, hide_until_ready) in blueprint_instances.iter() {
info!("Finalizing blueprint instance {:?}", name);
commands.entity(entity)
.remove::<SpawnHere>()
.remove::<BlueprintSpawning>()
.remove::<BlueprintReadyForPostProcess>()
.insert(BlueprintInstanceReady)
.insert(Visibility::Visible)
;
if hide_until_ready.is_some() {
println!("REVEAAAL");
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()});
}

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT OR Apache-2.0"
[dependencies]
bevy = { version = "0.14.0-rc.3", features = ["dynamic_linking"] }
bevy = { version = "0.14", features = ["dynamic_linking"] }
blenvy = { path = "../../crates/blenvy" }
# bevy_gltf_blueprints = { path = "../../crates/bevy_gltf_blueprints" }
# bevy_registry_export = { path = "../../crates/bevy_registry_export" }

View File

@ -4182,6 +4182,30 @@
"type": "object",
"typeInfo": "Struct"
},
"bevy_example::test_components::RedirectPropHitImpulse": {
"isComponent": true,
"isResource": false,
"long_name": "bevy_example::test_components::RedirectPropHitImpulse",
"oneOf": [
{
"items": false,
"long_name": "Local",
"prefixItems": [
{
"type": {
"$ref": "#/$defs/glam::Vec3"
}
}
],
"short_name": "Local",
"type": "array",
"typeInfo": "Tuple"
}
],
"short_name": "RedirectPropHitImpulse",
"type": "object",
"typeInfo": "Enum"
},
"bevy_example::test_components::TupleTest2": {
"isComponent": true,
"isResource": false,
@ -8145,11 +8169,6 @@
"$ref": "#/$defs/bevy_render::alpha::AlphaMode"
}
},
"anisotropy_channel": {
"type": {
"$ref": "#/$defs/bevy_pbr::pbr_material::UvChannel"
}
},
"anisotropy_rotation": {
"type": {
"$ref": "#/$defs/f32"
@ -8160,11 +8179,6 @@
"$ref": "#/$defs/f32"
}
},
"anisotropy_texture": {
"type": {
"$ref": "#/$defs/core::option::Option<bevy_asset::handle::Handle<bevy_render::texture::image::Image>>"
}
},
"attenuation_color": {
"type": {
"$ref": "#/$defs/bevy_color::color::Color"
@ -8374,7 +8388,6 @@
"clearcoat_perceptual_roughness",
"anisotropy_strength",
"anisotropy_rotation",
"anisotropy_channel",
"double_sided",
"unlit",
"fog_enabled",

View File

@ -1,5 +1,5 @@
use bevy::prelude::*;
use blenvy::{BluePrintBundle, BlueprintInfo, DynamicBlueprintInstance, GameWorldTag, SpawnHere};
use blenvy::{BluePrintBundle, BlueprintInfo, DynamicBlueprintInstance, GameWorldTag, HideUntilReady, SpawnHere};
use crate::{GameState, InAppRunning};
//use bevy_rapier3d::prelude::Velocity;
@ -23,6 +23,7 @@ pub fn setup_game(
commands.spawn((
BlueprintInfo{name: "World".into(), path: "levels/World.glb".into()},
HideUntilReady,
bevy::prelude::Name::from("world"), //FIXME: not really needed ? could be infered from blueprint's name/ path
SpawnHere,
GameWorldTag,
@ -67,6 +68,7 @@ pub fn spawn_test(
},
DynamicBlueprintInstance,
bevy::prelude::Name::from(format!("test{}", name_index)),
HideUntilReady,
// SpawnHere,
TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)),
/*Velocity {

View File

@ -129,10 +129,19 @@ fn exit_game(mut app_exit_events: ResMut<Events<bevy::app::AppExit>>) {
fn check_for_gltf_events(
mut blueprint_events: EventReader<BlueprintEvent>,
all_names: Query<&Name>,
)
{
for event in blueprint_events.read() {
info!("BLUEPRINT EVENT: {:?}", event);
match event {
BlueprintEvent::InstanceReady{entity, blueprint_name, blueprint_path} => {
info!("BLUEPRINT EVENT: {:?} for {:?}", event, all_names.get(*entity));
}
_=> {
info!("BLUEPRINT EVENT: {:?}", event);
}
}
}
}

View File

@ -1,7 +1,7 @@
use bevy::{gltf::{GltfMaterialExtras, GltfMeshExtras, GltfSceneExtras}, prelude::*};
use blenvy::{BlueprintAssets, BlueprintInstanceReady};
use crate::{BasicTest, EnumComplex};
use crate::{BasicTest, EnumComplex, RedirectPropHitImpulse};
#[derive(Component)]
pub struct HiearchyDebugTag;
@ -12,7 +12,7 @@ pub fn setup_hierarchy_debug(mut commands: Commands, asset_server: Res<AssetServ
TextBundle::from_section(
"",
TextStyle {
color: LinearRgba { red: 1.0, green:0.0, blue: 0.0, alpha: 1.0}.into(),
color: LinearRgba { red: 1.0, green:1.0, blue: 1.0, alpha: 1.0}.into(),
font_size: 15.,
..default()
},
@ -30,7 +30,10 @@ pub fn setup_hierarchy_debug(mut commands: Commands, asset_server: Res<AssetServ
pub fn get_descendants(
all_children: &Query<&Children>,
all_names:&Query<&Name>, root: &Entity,
all_names:&Query<&Name>,
root: &Entity,
all_transforms: &Query<&Transform>,
all_global_transforms: &Query<&GlobalTransform>,
nesting: usize,
to_check: &Query<&BasicTest>//&Query<(&BlueprintInstanceReady, &BlueprintAssets)>,
)
@ -48,14 +51,14 @@ pub fn get_descendants(
let components_to_check = to_check.get(*root);
hierarchy_display.push( format!("{}{} ({:?})", " ".repeat(nesting), name, components_to_check) ); //
hierarchy_display.push( format!("{}{} ({:?}) ({:?})", " ".repeat(nesting), name, all_transforms.get(*root), all_global_transforms.get(*root)) ); //components_to_check ({:?})
if let Ok(children) = all_children.get(*root) {
for child in children.iter() {
let child_descendants_display = get_descendants(&all_children, &all_names, &child, nesting + 4, &to_check);
let child_descendants_display = get_descendants(&all_children, &all_names, &child, &all_transforms, &all_global_transforms, nesting + 4, &to_check);
hierarchy_display.push(child_descendants_display);
}
}
@ -66,6 +69,8 @@ pub fn draw_hierarchy_debug(
root: Query<(Entity, Option<&Name>, &Children), (Without<Parent>)>,
all_children: Query<&Children>,
all_names:Query<&Name>,
all_transforms: Query<&Transform>,
all_global_transforms: Query<&GlobalTransform>,
to_check: Query<&BasicTest>,//Query<(&BlueprintInstanceReady, &BlueprintAssets)>,
mut display: Query<&mut Text, With<HiearchyDebugTag>>,
@ -75,7 +80,7 @@ pub fn draw_hierarchy_debug(
for (root_entity, name, children) in root.iter() {
// hierarchy_display.push( format!("Hierarchy root{:?}", name) );
hierarchy_display.push(get_descendants(&all_children, &all_names, &root_entity, 0, &to_check));
hierarchy_display.push(get_descendants(&all_children, &all_names, &root_entity, &all_transforms, &all_global_transforms, 0, &to_check));
// let mut children = all_children.get(root_entity);
/*for child in children.iter() {
// hierarchy_display
@ -134,13 +139,13 @@ for (id, name, scene_extras, extras, mesh_extras, material_extras) in
}
fn check_for_component(
foo: Query<(Entity, Option<&Name>, &EnumComplex)>,
foo: Query<(Entity, Option<&Name>, &RedirectPropHitImpulse)>,
mut display: Query<&mut Text, With<HiearchyDebugTag>>,
){
let mut info_lines: Vec<String> = vec![];
for (entiity, name , enum_complex) in foo.iter(){
let data = format!(" We have a 'EnumComplex' component: {:?} (on {:?})", enum_complex, name);
let data = format!(" We have a matching component: {:?} (on {:?})", enum_complex, name);
info_lines.push(data);
println!("yoho component");
@ -156,8 +161,8 @@ impl Plugin for HiearchyDebugPlugin {
fn build(&self, app: &mut App) {
app
.add_systems(Startup, setup_hierarchy_debug)
// .add_systems(Update, check_for_component)
.add_systems(Update, draw_hierarchy_debug)
//.add_systems(Update, check_for_component)
//.add_systems(Update, draw_hierarchy_debug)
//.add_systems(Update, check_for_gltf_extras)
;

View File

@ -224,6 +224,12 @@ pub struct ComponentWithFieldsOfIdenticalType{
#[reflect(Component)]
pub struct ComponentWithFieldsOfIdenticalType2(f32, f32, f32);
#[derive(Debug, Clone, Copy, PartialEq, Reflect, Component)]
#[reflect(Component, )]
pub enum RedirectPropHitImpulse {
Local(Vec3),
}
pub struct ComponentsTestPlugin;
impl Plugin for ComponentsTestPlugin {
fn build(&self, app: &mut App) {
@ -279,6 +285,8 @@ impl Plugin for ComponentsTestPlugin {
.register_type::<ComponentWithFieldsOfIdenticalType>()
.register_type::<ComponentWithFieldsOfIdenticalType2>()
.register_type::<RedirectPropHitImpulse>()
.add_plugins(MaterialPlugin::<
ExtendedMaterial<StandardMaterial, MyExtension>,
>::default());