Compare commits

...

6 Commits

Author SHA1 Message Date
Jan Hohenheim d0dad53253
Merge 67756dd2cd into d08c235122 2024-08-03 19:00:09 +02:00
Jan Hohenheim 67756dd2cd
Fix missing image 2024-08-03 19:00:00 +02:00
Jan Hohenheim b00ef0a9a8
React to feedback 2024-08-03 18:42:08 +02:00
kaosat.dev d08c235122 feat(examples:Save_load): created much simpler example for save load
* removed old assets & co
 * fresh blend with Blenvy logic
 * generated assets (wip)
 * started updating code (wip)
2024-08-03 01:29:34 +02:00
kaosat.dev 171ec7490a feat(Blenvy:Bevy): slowly adding back save/load (wip)
* moved out old code
 * added very basics of saving (HEAVY WIP)
2024-08-03 01:28:46 +02:00
kaosat.dev ae9f07f549 feat(Blenvy:Blender): started upgrading code relevant to save/load, dynamic entities etc
* using some of the newer components api to determine if an object/its collection is dynamic or not
2024-08-03 01:27:44 +02:00
50 changed files with 14951 additions and 1386 deletions

View File

@ -10,6 +10,9 @@ pub use registry::*;
pub mod blueprints; pub mod blueprints;
pub use blueprints::*; pub use blueprints::*;
pub mod save_load;
pub use save_load::*;
#[derive(Clone, Resource)] #[derive(Clone, Resource)]
pub struct BlenvyConfig { pub struct BlenvyConfig {
// registry // registry
@ -26,6 +29,8 @@ pub struct BlenvyConfig {
// save & load // save & load
pub(crate) save_component_filter: SceneFilter, pub(crate) save_component_filter: SceneFilter,
pub(crate) save_resource_filter: SceneFilter, pub(crate) save_resource_filter: SceneFilter,
//pub(crate) save_path: PathBuf,
// save_path: PathBuf::from("saves"),
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -63,6 +68,7 @@ impl Plugin for BlenvyPlugin {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
ExportRegistryPlugin::default(), ExportRegistryPlugin::default(),
BlueprintsPlugin::default(), BlueprintsPlugin::default(),
SaveLoadPlugin::default()
)) ))
.insert_resource(BlenvyConfig { .insert_resource(BlenvyConfig {
export_registry: self.export_registry, export_registry: self.export_registry,

View File

@ -1,108 +1,59 @@
pub mod saveable;
use std::path::PathBuf;
pub use saveable::*;
pub mod saving;
pub use saving::*;
pub mod loading;
pub use loading::*;
use bevy::core_pipeline::core_3d::{Camera3dDepthTextureUsage, ScreenSpaceTransmissionQuality};
use bevy::prelude::*; use bevy::prelude::*;
use bevy::prelude::{App, IntoSystemConfigs, Plugin};
use blenvy::GltfBlueprintsSet;
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] #[derive(Component, Reflect, Debug, Default)]
pub enum SavingSet { #[reflect(Component)]
Save, /// component used to mark any entity as Dynamic: aka add this to make sure your entity is going to be saved
} pub struct Dynamic;
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] #[derive(Component, Reflect, Debug, Default)]
pub enum LoadingSet { #[reflect(Component)]
Load, /// marker component for entities that do not have parents, or whose parents should be ignored when serializing
} pub(crate) struct RootEntity;
// Plugin configuration #[derive(Component, Debug)]
/// internal helper component to store parents before resetting them
pub(crate) struct OriginalParent(pub(crate) Entity);
#[derive(Clone, Resource)]
pub struct SaveLoadConfig {
pub(crate) save_path: PathBuf,
pub(crate) component_filter: SceneFilter,
pub(crate) resource_filter: SceneFilter,
}
// define the plugin
pub struct SaveLoadPlugin {
pub component_filter: SceneFilter,
pub resource_filter: SceneFilter,
pub save_path: PathBuf,
}
impl Default for SaveLoadPlugin {
fn default() -> Self {
Self {
component_filter: SceneFilter::default(),
resource_filter: SceneFilter::default(),
save_path: PathBuf::from("scenes"),
}
}
}
/// Marker component to Flag the root entity of all static entities (immutables)
#[derive(Component, Reflect, Debug, Default)] #[derive(Component, Reflect, Debug, Default)]
#[reflect(Component)] #[reflect(Component)]
pub struct StaticEntitiesRoot; pub struct StaticEntitiesRoot;
/// Marker component to Flag the root entity of all dynamic entities (mutables)
#[derive(Component, Reflect, Debug, Default)] #[derive(Component, Reflect, Debug, Default)]
#[reflect(Component)] #[reflect(Component)]
pub struct DynamicEntitiesRoot; pub struct DynamicEntitiesRoot;
#[derive(Resource, Clone, Debug, Default, Reflect)]
#[reflect(Resource)]
pub struct StaticEntitiesBlueprintInfo {
//pub blueprint_info: BlueprintInfo,
pub path: String,
}
pub mod saving;
pub use saving::*;
#[derive(Debug, Clone, Default)]
/// Plugin for saving & loading
pub struct SaveLoadPlugin {}
impl Plugin for SaveLoadPlugin { impl Plugin for SaveLoadPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.register_type::<Dynamic>() app.register_type::<Dynamic>()
.register_type::<StaticEntitiesRoot>() .register_type::<StaticEntitiesRoot>()
// TODO: remove these in bevy 0.13, as these are now registered by default
.register_type::<Camera3dDepthTextureUsage>()
.register_type::<ScreenSpaceTransmissionQuality>()
.register_type::<StaticEntitiesStorage>()
.add_event::<SaveRequest>()
.add_event::<LoadRequest>()
.add_event::<LoadingFinished>()
.add_event::<SavingFinished>()
.insert_resource(SaveLoadConfig {
save_path: self.save_path.clone(),
component_filter: self.component_filter.clone(), .add_event::<SaveRequest>()
resource_filter: self.resource_filter.clone(), .add_event::<SaveFinished>()
})
.configure_sets(
Update,
(LoadingSet::Load).chain().before(GltfBlueprintsSet::Spawn), //.before(GltfComponentsSet::Injection)
)
.add_systems( .add_systems(
PreUpdate, Update,
(prepare_save_game, apply_deferred, save_game, cleanup_save) (prepare_save_game, apply_deferred, save_game, cleanup_save)
.chain() .chain()
.run_if(should_save), .run_if(should_save),
) )
.add_systems(Update, mark_load_requested) ;
.add_systems(
Update,
(unload_world, apply_deferred, load_game)
.chain()
.run_if(resource_exists::<LoadRequested>)
.run_if(not(resource_exists::<LoadFirstStageDone>))
.in_set(LoadingSet::Load),
)
.add_systems(
Update,
(load_static, apply_deferred, cleanup_loaded_scene)
.chain()
.run_if(resource_exists::<LoadFirstStageDone>)
// .run_if(in_state(AppState::LoadingGame))
.in_set(LoadingSet::Load),
);
} }
} }

View File

@ -0,0 +1,76 @@
pub mod saveable;
use std::path::PathBuf;
pub use saveable::*;
pub mod saving;
pub use saving::*;
pub mod loading;
pub use loading::*;
use bevy::core_pipeline::core_3d::{Camera3dDepthTextureUsage, ScreenSpaceTransmissionQuality};
use bevy::prelude::*;
use bevy::prelude::{App, IntoSystemConfigs, Plugin};
use blenvy::GltfBlueprintsSet;
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
pub enum SavingSet {
Save,
}
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
pub enum LoadingSet {
Load,
}
#[derive(Component, Reflect, Debug, Default)]
#[reflect(Component)]
pub struct StaticEntitiesRoot;
#[derive(Component, Reflect, Debug, Default)]
#[reflect(Component)]
pub struct DynamicEntitiesRoot;
impl Plugin for SaveLoadPlugin {
fn build(&self, app: &mut App) {
app.register_type::<Dynamic>()
.register_type::<StaticEntitiesRoot>()
// TODO: remove these in bevy 0.13, as these are now registered by default
.register_type::<Camera3dDepthTextureUsage>()
.register_type::<ScreenSpaceTransmissionQuality>()
.register_type::<StaticEntitiesStorage>()
.add_event::<SaveRequest>()
.add_event::<LoadRequest>()
.add_event::<LoadingFinished>()
.add_event::<SavingFinished>()
.configure_sets(
Update,
(LoadingSet::Load).chain().before(GltfBlueprintsSet::Spawn), //.before(GltfComponentsSet::Injection)
)
.add_systems(
PreUpdate,
(prepare_save_game, apply_deferred, save_game, cleanup_save)
.chain()
.run_if(should_save),
)
.add_systems(Update, mark_load_requested)
.add_systems(
Update,
(unload_world, apply_deferred, load_game)
.chain()
.run_if(resource_exists::<LoadRequested>)
.run_if(not(resource_exists::<LoadFirstStageDone>))
.in_set(LoadingSet::Load),
)
.add_systems(
Update,
(load_static, apply_deferred, cleanup_loaded_scene)
.chain()
.run_if(resource_exists::<LoadFirstStageDone>)
// .run_if(in_state(AppState::LoadingGame))
.in_set(LoadingSet::Load),
);
}
}

View File

@ -0,0 +1,196 @@
use bevy::prelude::*;
use bevy::tasks::IoTaskPool;
use blenvy::{BlueprintName, InBlueprint, Library, SpawnHere};
use std::fs::File;
use std::io::Write;
use std::path::Path;
use crate::{DynamicEntitiesRoot, SaveLoadConfig, StaticEntitiesRoot};
#[derive(Event, Debug)]
pub struct SaveRequest {
pub path: String,
}
#[derive(Event)]
pub struct SavingFinished;
pub fn should_save(save_requests: EventReader<SaveRequest>) -> bool {
!save_requests.is_empty()
}
#[derive(Resource, Clone, Debug, Default, Reflect)]
#[reflect(Resource)]
pub struct StaticEntitiesStorage {
pub name: String,
pub library_path: String,
}
#[derive(Component, Reflect, Debug, Default)]
#[reflect(Component)]
/// marker component for entities that do not have parents, or whose parents should be ignored when serializing
pub(crate) struct RootEntity;
#[derive(Component, Debug)]
/// internal helper component to store parents before resetting them
pub(crate) struct OriginalParent(pub(crate) Entity);
// any child of dynamic/ saveable entities that is not saveable itself should be removed from the list of children
pub(crate) fn prepare_save_game(
saveables: Query<Entity, (With<Dynamic>, With<BlueprintName>)>,
root_entities: Query<Entity, Or<(With<DynamicEntitiesRoot>, Without<Parent>)>>, // With<DynamicEntitiesRoot>
dynamic_entities: Query<(Entity, &Parent, Option<&Children>), With<Dynamic>>,
static_entities: Query<(Entity, &BlueprintName, Option<&Library>), With<StaticEntitiesRoot>>,
mut commands: Commands,
) {
for entity in saveables.iter() {
commands.entity(entity).insert(SpawnHere);
}
for (entity, parent, children) in dynamic_entities.iter() {
let parent = parent.get();
if root_entities.contains(parent) {
commands.entity(entity).insert(RootEntity);
}
if let Some(children) = children {
for sub_child in children.iter() {
if !dynamic_entities.contains(*sub_child) {
commands.entity(*sub_child).insert(OriginalParent(entity));
commands.entity(entity).remove_children(&[*sub_child]);
}
}
}
}
for (_, blueprint_name, library) in static_entities.iter() {
let library_path: String = library.map_or_else(|| "", |l| l.0.to_str().unwrap()).into();
commands.insert_resource(StaticEntitiesStorage {
name: blueprint_name.0.clone(),
library_path,
});
}
}
pub(crate) fn save_game(world: &mut World) {
info!("saving");
let mut save_path: String = "".into();
let mut events = world.resource_mut::<Events<SaveRequest>>();
for event in events.get_reader().read(&events) {
info!("SAVE EVENT !! {:?}", event);
save_path.clone_from(&event.path);
}
events.clear();
let saveable_entities: Vec<Entity> = world
.query_filtered::<Entity, (With<Dynamic>, Without<InBlueprint>, Without<RootEntity>)>()
.iter(world)
.collect();
let saveable_root_entities: Vec<Entity> = world
.query_filtered::<Entity, (With<Dynamic>, Without<InBlueprint>, With<RootEntity>)>()
.iter(world)
.collect();
info!("saveable entities {}", saveable_entities.len());
info!("saveable root entities {}", saveable_root_entities.len());
let save_load_config = world
.get_resource::<SaveLoadConfig>()
.expect("SaveLoadConfig should exist at this stage");
// we hardcode some of the always allowed types
let filter = save_load_config
.component_filter
.clone()
.allow::<Parent>()
.allow::<Children>()
.allow::<BlueprintName>()
.allow::<SpawnHere>()
.allow::<Dynamic>()
;
// for root entities, it is the same EXCEPT we make sure parents are not included
let filter_root = filter.clone().deny::<Parent>();
let filter_resources = save_load_config
.resource_filter
.clone()
.allow::<StaticEntitiesStorage>()
;
// for default stuff
let scene_builder = DynamicSceneBuilder::from_world(world)
.with_filter(filter.clone())
.with_resource_filter(filter_resources.clone());
let mut dyn_scene = scene_builder
.extract_resources()
.extract_entities(saveable_entities.clone().into_iter())
.remove_empty_entities()
.build();
// for root entities
let scene_builder_root = DynamicSceneBuilder::from_world(world)
.with_filter(filter_root.clone())
.with_resource_filter(filter_resources.clone());
// FIXME : add back
let mut dyn_scene_root = scene_builder_root
.extract_resources()
.extract_entities(
saveable_root_entities.clone().into_iter(), // .chain(static_world_markers.into_iter()),
)
.remove_empty_entities()
.build();
dyn_scene.entities.append(&mut dyn_scene_root.entities);
// dyn_scene.resources.append(&mut dyn_scene_root.resources);
let serialized_scene = dyn_scene
.serialize(&world.resource::<AppTypeRegistry>().read())
.unwrap();
let save_path = Path::new("assets")
.join(&save_load_config.save_path)
.join(Path::new(save_path.as_str())); // Path::new(&save_load_config.save_path).join(Path::new(save_path.as_str()));
info!("saving game to {:?}", save_path);
// world.send_event(SavingFinished);
#[cfg(not(target_arch = "wasm32"))]
IoTaskPool::get()
.spawn(async move {
// Write the scene RON data to file
File::create(save_path)
.and_then(|mut file| file.write(serialized_scene.as_bytes()))
.expect("Error while writing save to file");
})
.detach();
}
pub(crate) fn cleanup_save(
needs_parent_reset: Query<(Entity, &OriginalParent)>,
mut saving_finished: EventWriter<SavingFinished>,
mut commands: Commands,
) {
for (entity, original_parent) in needs_parent_reset.iter() {
commands.entity(original_parent.0).add_child(entity);
}
commands.remove_resource::<StaticEntitiesStorage>();
saving_finished.send(SavingFinished);
}
/*
pub(crate) fn cleanup_save(mut world: &mut World) {
let mut query = world.query::<(Entity, &OriginalParent)>();
for (mut entity, original_parent) in query.iter_mut(&mut world) {
let e = world.entity_mut(original_parent.0);
// .add_child(entity);
}
}*/

View File

@ -1,7 +0,0 @@
use bevy::prelude::*;
#[derive(Component, Reflect, Debug, Default)]
#[reflect(Component)]
/// component used to mark any entity as Dynamic: aka add this to make sure your entity is going to be saved
pub struct Dynamic(pub bool);

View File

@ -1,55 +1,42 @@
use bevy::prelude::*;
use bevy::tasks::IoTaskPool;
use blenvy::{BlueprintName, InBlueprint, Library, SpawnHere};
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
use std::path::Path; use std::path::Path;
use crate::{Dynamic, DynamicEntitiesRoot, SaveLoadConfig, StaticEntitiesRoot}; use bevy::render::camera::{CameraMainTextureUsages, CameraRenderGraph};
use bevy::{prelude::*, tasks::IoTaskPool};
use bevy::prelude::World;
use crate::{BlenvyConfig, BlueprintInfo, Dynamic, FromBlueprint, RootEntity, SpawnBlueprint};
use super::{DynamicEntitiesRoot, OriginalParent, StaticEntitiesRoot};
#[derive(Event, Debug)] #[derive(Event, Debug)]
pub struct SaveRequest { pub struct SaveRequest {
pub path: String, pub path: String,
} }
#[derive(Event)] #[derive(Event)]
pub struct SavingFinished; pub struct SaveFinished; // TODO: merge the the events above
pub fn should_save(save_requests: EventReader<SaveRequest>) -> bool { pub fn should_save(save_requests: EventReader<SaveRequest>) -> bool {
!save_requests.is_empty() !save_requests.is_empty()
} }
#[derive(Resource, Clone, Debug, Default, Reflect)]
#[reflect(Resource)]
pub struct StaticEntitiesStorage {
pub name: String,
pub library_path: String,
}
#[derive(Component, Reflect, Debug, Default)]
#[reflect(Component)]
/// marker component for entities that do not have parents, or whose parents should be ignored when serializing
pub(crate) struct RootEntity;
#[derive(Component, Debug)]
/// internal helper component to store parents before resetting them
pub(crate) struct OriginalParent(pub(crate) Entity);
// any child of dynamic/ saveable entities that is not saveable itself should be removed from the list of children // any child of dynamic/ saveable entities that is not saveable itself should be removed from the list of children
pub(crate) fn prepare_save_game( pub(crate) fn prepare_save_game(
saveables: Query<Entity, (With<Dynamic>, With<BlueprintName>)>, saveables: Query<Entity, (With<Dynamic>, With<BlueprintInfo>)>,
root_entities: Query<Entity, Or<(With<DynamicEntitiesRoot>, Without<Parent>)>>, // With<DynamicEntitiesRoot> root_entities: Query<Entity, Or<(With<DynamicEntitiesRoot>, Without<Parent>)>>, // With<DynamicEntitiesRoot>
dynamic_entities: Query<(Entity, &Parent, Option<&Children>), With<Dynamic>>, dynamic_entities: Query<(Entity, &Parent, Option<&Children>), With<Dynamic>>,
static_entities: Query<(Entity, &BlueprintName, Option<&Library>), With<StaticEntitiesRoot>>, static_entities: Query<(Entity, &BlueprintInfo), With<StaticEntitiesRoot>>,
mut commands: Commands, mut commands: Commands,
) { ) {
for entity in saveables.iter() { for entity in saveables.iter() {
commands.entity(entity).insert(SpawnHere); commands.entity(entity).insert(SpawnBlueprint);
} }
for (entity, parent, children) in dynamic_entities.iter() { for (entity, parent, children) in dynamic_entities.iter() {
println!("prepare save game");
let parent = parent.get(); let parent = parent.get();
if root_entities.contains(parent) { if root_entities.contains(parent) {
commands.entity(entity).insert(RootEntity); commands.entity(entity).insert(RootEntity);
@ -64,14 +51,17 @@ pub(crate) fn prepare_save_game(
} }
} }
} }
for (_, blueprint_name, library) in static_entities.iter() { /*for (_, blueprint_name) in static_entities.iter() {
let library_path: String = library.map_or_else(|| "", |l| l.0.to_str().unwrap()).into(); let library_path: String = library.map_or_else(|| "", |l| l.0.to_str().unwrap()).into();
commands.insert_resource(StaticEntitiesStorage { commands.insert_resource(StaticEntitiesStorage {
name: blueprint_name.0.clone(), name: blueprint_name.0.clone(),
library_path, library_path,
}); });
} }*/
} }
pub(crate) fn save_game(world: &mut World) { pub(crate) fn save_game(world: &mut World) {
info!("saving"); info!("saving");
@ -85,39 +75,49 @@ pub(crate) fn save_game(world: &mut World) {
events.clear(); events.clear();
let saveable_entities: Vec<Entity> = world let saveable_entities: Vec<Entity> = world
.query_filtered::<Entity, (With<Dynamic>, Without<InBlueprint>, Without<RootEntity>)>() // .query_filtered::<Entity, (With<Dynamic>, Without<FromBlueprint>, Without<RootEntity>)>()
.query_filtered::<Entity, (With<Dynamic>, Without<RootEntity>)>()
.iter(world) .iter(world)
.collect(); .collect();
let saveable_root_entities: Vec<Entity> = world let saveable_root_entities: Vec<Entity> = world
.query_filtered::<Entity, (With<Dynamic>, Without<InBlueprint>, With<RootEntity>)>() .query_filtered::<Entity, (With<Dynamic>, With<RootEntity>)>()
//.query_filtered::<Entity, (With<Dynamic>, Without<FromBlueprint>, With<RootEntity>)>()
.iter(world) .iter(world)
.collect(); .collect();
info!("saveable entities {}", saveable_entities.len()); info!("saveable entities {}", saveable_entities.len());
info!("saveable root entities {}", saveable_root_entities.len()); info!("saveable root entities {}", saveable_root_entities.len());
let save_load_config = world let config = world
.get_resource::<SaveLoadConfig>() .get_resource::<BlenvyConfig>()
.expect("SaveLoadConfig should exist at this stage"); .expect("Blenvy configuration should exist at this stage");
// we hardcode some of the always allowed types // we hardcode some of the always allowed types
let filter = save_load_config let filter = config
.component_filter .save_component_filter
.clone() .clone()
.allow::<Parent>() .allow::<Parent>()
.allow::<Children>() .allow::<Children>()
.allow::<BlueprintName>() .allow::<BlueprintInfo>()
.allow::<SpawnHere>() .allow::<SpawnBlueprint>()
.allow::<Dynamic>(); .allow::<Dynamic>()
/*.deny::<CameraRenderGraph>()
.deny::<CameraMainTextureUsages>()
.deny::<Handle<Mesh>>()
.deny::<Handle<StandardMaterial>>() */
;
// for root entities, it is the same EXCEPT we make sure parents are not included // for root entities, it is the same EXCEPT we make sure parents are not included
let filter_root = filter.clone().deny::<Parent>(); let filter_root = filter.clone().deny::<Parent>();
let filter_resources = save_load_config let filter_resources = config.clone()
.resource_filter .save_resource_filter
.clone() .deny::<Time<Real>>()
.allow::<StaticEntitiesStorage>();
.clone();
//.allow::<StaticEntitiesStorage>();
// for default stuff // for default stuff
let scene_builder = DynamicSceneBuilder::from_world(world) let scene_builder = DynamicSceneBuilder::from_world(world)
@ -143,15 +143,15 @@ pub(crate) fn save_game(world: &mut World) {
.remove_empty_entities() .remove_empty_entities()
.build(); .build();
dyn_scene.entities.append(&mut dyn_scene_root.entities); // dyn_scene.entities.append(&mut dyn_scene_root.entities);
// dyn_scene.resources.append(&mut dyn_scene_root.resources); // dyn_scene.resources.append(&mut dyn_scene_root.resources);
let serialized_scene = dyn_scene let serialized_scene = dyn_scene
.serialize(&world.resource::<AppTypeRegistry>().read()) .serialize(&world.resource::<AppTypeRegistry>().read())
.unwrap(); .expect("filtered scene should serialize correctly");
let save_path = Path::new("assets") let save_path = Path::new("assets")
.join(&save_load_config.save_path) //.join(&config.save_path)
.join(Path::new(save_path.as_str())); // Path::new(&save_load_config.save_path).join(Path::new(save_path.as_str())); .join(Path::new(save_path.as_str())); // Path::new(&save_load_config.save_path).join(Path::new(save_path.as_str()));
info!("saving game to {:?}", save_path); info!("saving game to {:?}", save_path);
@ -170,21 +170,12 @@ pub(crate) fn save_game(world: &mut World) {
pub(crate) fn cleanup_save( pub(crate) fn cleanup_save(
needs_parent_reset: Query<(Entity, &OriginalParent)>, needs_parent_reset: Query<(Entity, &OriginalParent)>,
mut saving_finished: EventWriter<SavingFinished>, mut saving_finished: EventWriter<SaveFinished>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (entity, original_parent) in needs_parent_reset.iter() { for (entity, original_parent) in needs_parent_reset.iter() {
commands.entity(original_parent.0).add_child(entity); commands.entity(original_parent.0).add_child(entity);
} }
commands.remove_resource::<StaticEntitiesStorage>(); // commands.remove_resource::<StaticEntitiesStorage>();
saving_finished.send(SavingFinished); saving_finished.send(SaveFinished);
} }
/*
pub(crate) fn cleanup_save(mut world: &mut World) {
let mut query = world.query::<(Entity, &OriginalParent)>();
for (mut entity, original_parent) in query.iter_mut(&mut world) {
let e = world.entity_mut(original_parent.0);
// .add_child(entity);
}
}*/

View File

Before

Width:  |  Height:  |  Size: 459 KiB

After

Width:  |  Height:  |  Size: 459 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 KiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

After

Width:  |  Height:  |  Size: 149 KiB

View File

@ -3,6 +3,20 @@
This guide assumes that you have a basic Blenvy setup ready to tinker in. This guide assumes that you have a basic Blenvy setup ready to tinker in.
If you don't have that yet, please refer to the [quickstart](../quickstart/readme.md) guide. If you don't have that yet, please refer to the [quickstart](../quickstart/readme.md) guide.
## Table of Contents
- [Add Avian to Bevy](#add-avian-to-bevy)
- [Prepare your Scenes](#prepare-your-scenes)
- [Create a Rigid Body](#create-a-rigid-body)
- [Add Primitive Colliders](#add-primitive-colliders)
- [Direct](#direct)
- [With Empty](#with-empty)
- [Wireframes](#wireframes)
- [Add Dynamic Colliders](#add-dynamic-colliders)
- [Convex](#convex)
- [Concave](#concave)
- [Other useful components](#other-useful-components)
## Add Avian to Bevy ## Add Avian to Bevy
No big surprises here. Simply add `avian3d` as a dependency by running the following from your project root: No big surprises here. Simply add `avian3d` as a dependency by running the following from your project root:
@ -42,76 +56,41 @@ fn setup(mut commands: Commands) {
Run this once with `cargo.run` to generate a `registry.json` that contains the Avian components. Run this once with `cargo.run` to generate a `registry.json` that contains the Avian components.
## Prepare the Blueprints ## Prepare your Scenes
Set up your `World` and `Library` scenes in Blender. Switch to the `Library` scene. Set up your `World` and `Library` scenes in Blender.
Go into your `World` scene. If you are coming from the [quickstart guide](../quickstart/readme.md), you can remove the `Player` instance as we don't need it in this guide.
If you have created this scene yourself in advance, make sure that it contains a camera, a light, and some kind of ground.
Since the objects are quite big, you may need to move the camera a bit further away to see them all.
We set its Y location to `-15` and the X rotation to `90` for this reason.
Pressing `0` on your numpad will show you a preview of what the camera sees.
For reference, this is how our world setup looks:
<details>
<summary>The world setup before adding any physics</summary>
<img src="img/empty_world.png" width = 100%/>
</details>
Now switch to the `Library` scene.
If you're coming from the [quickstart](../quickstart/readme.md) guide, you may now delete the `Player` collection by If you're coming from the [quickstart](../quickstart/readme.md) guide, you may now delete the `Player` collection by
right-clicking it in the outliner and selecting `Delete Hierarchy` as we don't need it in this guide. right-clicking it in the outliner and selecting `Delete Hierarchy`.
Remember, you can find the outliner all the way to the right. Remember, you can find the outliner all the way to the right.
We will be showing different ways to add colliders, so we need to add a blueprint for each approach. ## Create a Rigid Body
Create three new collections in the outliner by doing `rightclick` -> `New Collection` and name them as follows:
- Cube Create a new collection with `rightclick` -> `New Collection` and name it `Direct`. This name will make sense in the next section.
- Board
- Cylinder
Your outliner should now look like this: Click on the `Direct` collection we just created to select it. Then, go to `Add` -> `Mesh` -> `Cube` in the upper left corner to add a cube to the collection. Leave it at the default transform.
<details>
<summary>Our empty collections</summary>
<img src="img/empty_collections.png" width = 50%/>
</details>
If you accidentally created a collection as a child of another, simply drag-and-drop them around to reorder them until they look like the image above.
### Cube
Click on the `Cube` collection we just created to select it. Then, go to `Add` -> `Mesh` -> `Cube` in the upper left corner to add a cube to the collection. Leave it at the default transform.
### Board
Click on the `Board` collection. Again, go to `Add` -> `Mesh` -> `Cube`. This time, scale it until it looks like a flat board:
<details>
<summary>The cylinder in Blender</summary>
<img src="img/cylinder.png" width = 50%/>
</details>
> [!TIP]
> The above screenshot was made after disabling the visibility of the `Cube` collection by clicking the eye icon in the outliner.
>
> <details>
> <summary>Hiding objects</summary>
> <img src="img/hiding.png" width = 50%/>
> </details>
>
> Hiding other collections becomes quickly essential when working with blueprints.
The scaling we used was the following:
- X: `2.5`
- Y: `0.5`
- Z: `1.5`
### Cylinder
Finally, click on the `Cylinder` collection. Go to `Add` -> `Mesh` -> `Cylinder`. Leave it at the default transform.
You should now have three collections with different shapes in them:
<details>
<summary>Collections with objects in the outliner</summary>
<img src="img/three_object_collection.png" width = 50%/>
</details>
## Add RigidBody Components
Avian makes a distinction between a *rigid body* and its associated *colliders*. Avian makes a distinction between a *rigid body* and its associated *colliders*.
In general, the best practice is to have a parent object be a rigid body and then have at least one descendant object be a collider. In general, the best practice is to have a parent object be a rigid body and then have at least one descendant object be a collider.
Adding the `RigidBody` is the same for all approaches: Add the `RigidBody` as follows:
- select the object in the viewport - select the object in the viewport, i.e. the cube.
- go to the Blenvy menu's component manager. Remember, if are missing the side menu, you can open it with `N`. - go to the Blenvy menu's component manager. Remember, if are missing the side menu, you can open it with `N`.
- type `rigidbody` in the search bar - type `rigidbody` in the search bar
- select `avian3d::dynamics::rigid_body::RigidBody` - select `avian3d::dynamics::rigid_body::RigidBody`
@ -129,7 +108,7 @@ The result should look like this:
</details> </details>
The default value for `RigidBody` is `Dynamic`, which is what we want for all three objects. The default value for `RigidBody` is `Dynamic`, which is what we want for all three objects.
It means that they will be affected by gravity and other forces. Repeat this step for the `Board` and `Cylinder` objects. It means that they will be affected by gravity and other forces.
## Add Primitive Colliders ## Add Primitive Colliders
@ -137,9 +116,9 @@ Colliders come in two flavors: primitive and dynamic. Primitives are made up of
There are three different ways to add primitive colliders to the objects, in order of increasing complexity. There are three different ways to add primitive colliders to the objects, in order of increasing complexity.
### Quick and Dirty ### Direct
Select the cube and search in the components for `colliderconstructor`. Select `avian3d::collision::collider::constructor::ColliderConstructor` and add it. Select the cube we just created and search in the components for `colliderconstructor`. Select `avian3d::collision::collider::constructor::ColliderConstructor` and add it.
By default, the collider will be of the variant `Sphere`. Change it to `Cuboid`. By default, the collider will be of the variant `Sphere`. Change it to `Cuboid`.
Since the standard cube in Blender is of size 2 m, set the `x_length`, `y_length`, and `z_length` all to `2.0`: Since the standard cube in Blender is of size 2 m, set the `x_length`, `y_length`, and `z_length` all to `2.0`:
<details> <details>
@ -153,10 +132,83 @@ That's already it.
> This method brings a major footgun: Blender uses Z-up coordinates, while Bevy uses Y-up coordinates. > This method brings a major footgun: Blender uses Z-up coordinates, while Bevy uses Y-up coordinates.
> The information you enter into the `ColliderConstructor` is in Bevy's coordinate system, so don't mix them up! > The information you enter into the `ColliderConstructor` is in Bevy's coordinate system, so don't mix them up!
### Using Empties To see it in action, we switch to the `World` scene and add and instance of our `Direct` collection with `Add` -> `Collection Instance`.
<details>
<summary>The world scene with the direct collider cube</summary>
<img src="img/direct_in_world.png" width = 50%/>
</details>
Save the scene to let Blenvy export everything and run the game with `cargo run`.
<details>
<summary>The cube falls down</summary>
<img src="img/falling_direct.gif" width = 50%/>
</details>
If everything went right, your cube should fall into the void due to gravity.
Note that it phases right through the ground because we have not yet added a rigid body and collider to it yet.
Click on the ground and add a `RigidBody` component as described before to it, but this time set it to `Static`.
This means that the ground itself will not react to forces such as gravity, but will still affect other rigid bodies.
Add a collider to the ground as before. Make sure that the dimensions of the collider match the dimensions of the ground.
<details>
<summary>Ground collider</summary>
<img src="img/ground collider.png" width = 100%/>
</details>
> [!CAUTION]
> As mentioned before, when using this method you should be aware that the component
> is in Bevy's coordinate system, so set the `y_length` to the *height* of the ground.
Run your game again with `cargo run` to see the cube landing on the ground.
<details>
<summary>The cube falls onto the ground</summary>
<img src="img/falling_direct_on_static.gif" width = 50%/>
</details>
> [!TIP]
> If your scene is doing something weird, try adding Avian's
> [`PhysicsDebugPlugin`](https://docs.rs/avian3d/latest/avian3d/debug_render/struct.PhysicsDebugPlugin.html)
> to your Bevy app to see the colliders at runtime.
> If the collider looks flipped, try switching the Y and Z lengths.
### With Empty
Go back to the `Library` scene. Add a collection named `With Empty`.
> [!TIP]
> If you accidentally created a collection as a child of another, simply drag-and-drop them around to reorder them
With the new collection selected, go to `Add` -> `Mesh` -> `Cube`. Name the new object `Board`.
This time, scale it until it looks like a flat board:
<details>
<summary>The board in Blender</summary>
<img src="img/board.png" width = 50%/>
</details>
> [!TIP]
> The above screenshot was made after disabling the visibility of the `Direct` collection by clicking the eye icon in the outliner.
>
> <details>
> <summary>Hiding objects</summary>
> <img src="img/hiding.png" width = 50%/>
> </details>
>
> Hiding other collections becomes quickly essential when working with blueprints.
The scaling we used was the following:
- X: `2.5`
- Y: `0.5`
- Z: `1.5`
You'll notice that the last variant does not actually show you a preview of the collider. Let's fix that. You'll notice that the last variant does not actually show you a preview of the collider. Let's fix that.
Click on the `Board` and then select `Add` -> `Empty` -> `Cube`. Click on the `With Empty` collection and then select `Add` -> `Empty` -> `Cube`.
To make its properties a bit nice to work with, go to the `Data` tab of the `Properties` window in the lower right: To make its properties a bit nice to work with, go to the `Data` tab of the `Properties` window in the lower right:
<details> <details>
@ -171,7 +223,7 @@ You'll notice that it says "Size: 1m". This is a little bit misleading, as we've
<img src="img/data.png" width = 50%/> <img src="img/data.png" width = 50%/>
</details> </details>
Add a collider to this empty like you did in the ["Quick and Dirty" section](#quick-and-dirty). Add a collider to this empty like you did in the ["Direct" section](#direct).
Set its lengths to `1` this time. Set its lengths to `1` this time.
If you have only the `Empty` set to visible and selected it, your viewport should now look as follows: If you have only the `Empty` set to visible and selected it, your viewport should now look as follows:
@ -235,7 +287,18 @@ You can just use these values as the scale for the `Empty`. After everything is
Note that the orange collider outlines should align nicely with the board's mesh. Note that the orange collider outlines should align nicely with the board's mesh.
### Using Wireframes Add an instance of the `With Empty` collection to the `World` scene just as before and run the game.
You should now see both objects fall to the ground.
<details>
<summary>The cube and board falling to the ground</summary>
<img src="img/falling_empty.gif" width = 50%/>
</details>
### Wireframes
Add a new collection named `Wireframe`. With it selected,
go to `Add` -> `Mesh` -> `Cylinder`. Leave it at the default transform.
The last variant is a bit of a workaround for the fact that empties in Blender cannot have an arbitrary shape. The last variant is a bit of a workaround for the fact that empties in Blender cannot have an arbitrary shape.
For example, a cylinder is not supported. So, we are going to create a new cylinder preview by hand. For example, a cylinder is not supported. So, we are going to create a new cylinder preview by hand.
@ -289,46 +352,13 @@ The rest of the steps are identical to the empty: Drag-and-drop the cylinder col
> You can use the builtin [Add Mesh Extra Objects](https://docs.blender.org/manual/en/latest/addons/add_mesh/mesh_extra_objects.html) > You can use the builtin [Add Mesh Extra Objects](https://docs.blender.org/manual/en/latest/addons/add_mesh/mesh_extra_objects.html)
> extension to fill this gap. > extension to fill this gap.
## Populate the world Add an instance of the `Wireframe` collection to the `World` scene and run the game to see all kinds of primitive colliders tumble around.
Go into your `World` scene. If you are coming from the [quickstart guide](../quickstart/readme.md), you can remove the `Player` empty that is left over.
If you have created this scene yourself in advance, make sure that it contains a camera, a light, and some kind of ground.
For reference, this is how our world setup looks:
<details> <details>
<summary>The world setup before adding any physics</summary> <summary>Cylinder collider falling down</summary>
<img src="img/empty_world.png" width = 100%/> <img src="img/falling_wireframe.gif" width = 50%/>
</details> </details>
Before we add any objects, we'll make the ground a rigid body as well. Add a `RigidBody` component as described before to it, but this time set it to `Static`. Add a collider to it in any of the ways described above. We used the `Quick and Dirty` method for this:
<details>
<summary>Ground collider</summary>
<img src="img/ground collider.png" width = 100%/>
</details>
> [!CAUTION]
> As mentioned before, when using this method you should be aware that the component
> is in Bevy's coordinate system, so set the `y_length` to the height of the ground.
Now add instances of the `Cube`, `Board`, and `Cylinder` to the world by selecting `Add` -> `Collection Instance`.
Since the objects are quite big, you may need to move the camera a bit further away to see them all.
We set its Y location to `-15` and the X rotation to `90` for this reason.
Pressing `0` on your numpad will show you a preview of what the camera sees.
Save the scene to let Blenvy export everything. Run your game with `cargo run` and you should see some objects falling onto the ground!
<details>
<summary>Objects falling onto the ground</summary>
<img src="img/falling.gif" width = 100%/>
</details>
> [!TIP]
> If your scene is doing something weird, try adding Avian's
> [`PhysicsDebugPlugin`](https://docs.rs/avian3d/latest/avian3d/debug_render/struct.PhysicsDebugPlugin.html)
> to your Bevy app to see the colliders at runtime.
## Add Dynamic Colliders ## Add Dynamic Colliders
Now let's go for some more complex shapes. Now let's go for some more complex shapes.
@ -337,7 +367,7 @@ or just quickly want to test something. For this, we are going to use dynamic co
### Convex ### Convex
Go back to the `Library` scene, add a new collection, and name it `Torus`. Select `Add` -> `Mesh` -> `Torus`. Leave it at the default transform. Add a `RigidBody` to it. Your scene should now look like this: Go back to the `Library` scene, add a new collection, and name it `Convex`. Select `Add` -> `Mesh` -> `Torus`. Leave it at the default transform. Add a `RigidBody` to it. Your scene should now look like this:
<details> <details>
<summary>A simple torus</summary> <summary>A simple torus</summary>
@ -355,7 +385,7 @@ You can access it by expanding your object in the outliner. Its icon is a green
<details> <details>
<summary>The selected mesh</summary> <summary>The selected mesh</summary>
<img src="img/selected_mesh.png" width = 50%/> <img src="img/select_mesh.png" width = 50%/>
</details> </details>
With the *mesh* selected, add a `ColliderConstructor` to it. Set the variant to `ConvexHullFromMesh`. With the *mesh* selected, add a `ColliderConstructor` to it. Set the variant to `ConvexHullFromMesh`.
@ -366,11 +396,21 @@ If you did everything correctly, the component manager should say "Components fo
<img src="img/torus_component.png" width = 50%/> <img src="img/torus_component.png" width = 50%/>
</details> </details>
That's all for now Go to the `World` scene and add an instance of the `Convex` collection. Save the scene, then run the game to see the torus fall down.
<details>
<summary>The convex collider falling</summary>
<img src="img/falling_convex.gif" width = 50%/>
</details>
> [!TIP]
> Is your game crashing with `Tried to add a collider to entity Torus via ConvexHullFromMesh that requires a mesh, but no mesh handle was found`?
> That means you added your `ColliderConstructor` to the object instead of the mesh.
> Go back to the screenshots above and make sure you have the mesh selected when adding the component.
### Concave ### Concave
Add a new collection and name it `Monkey`. Select `Add` -> `Mesh` -> `Monkey`. Add a new collection and name it `Concave`. Select `Add` -> `Mesh` -> `Monkey`.
Yes, Blender has a builtin method for creating Suzanne, its monkey mascot. Isn't it great? Yes, Blender has a builtin method for creating Suzanne, its monkey mascot. Isn't it great?
Anyways, add a rigid body to it. Afterwards, just as before, select the *mesh* of the monkey. Anyways, add a rigid body to it. Afterwards, just as before, select the *mesh* of the monkey.
Add a `ColliderConstructor` to it. This time, set the variant to `TrimeshFromMesh`. Add a `ColliderConstructor` to it. This time, set the variant to `TrimeshFromMesh`.
@ -381,22 +421,13 @@ Add a `ColliderConstructor` to it. This time, set the variant to `TrimeshFromMes
> That means that any objects that are completely inside the mesh will not collide with it. > That means that any objects that are completely inside the mesh will not collide with it.
> Only use a concave collider if you *really* need it. > Only use a concave collider if you *really* need it.
## Add the Dynamic Colliders to the World Just as before, go to the `World` scene and add an instance of the `Concave` collection. Save the scene, then run the game to see the torus fall down.
Save the scene to let Blenvy export everything.
Go back to the `World` scene. Add instances of the `Torus` and `Monkey` collections to the world and run the game with `cargo run`.
They should now fall onto the ground and interact with the other objects:
<details> <details>
<summary>The primitive and dynamic colliders falling down</summary> <summary>The concave collider falling</summary>
<img src="img/falling_dyn.gif" width = 100%/> <img src="img/falling_concave.gif" width = 50%/>
</details> </details>
> [!TIP]
> Is your game crashing with `Tried to add a collider to entity Torus via <ConvexHullFromMesh or TrimeshFromMesh> that requires a mesh, but no mesh handle was found`?
> That means you added your `ColliderConstructor` to the object instead of the mesh.
> Go back to the screenshots above and make sure you have the mesh selected when adding the component.
## Other useful components ## Other useful components
The object holding the `ColliderConstructor` can hold some additional components that are useful for tweaking the physics behavior. The object holding the `ColliderConstructor` can hold some additional components that are useful for tweaking the physics behavior.

Binary file not shown.

View File

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

View File

@ -1,8 +0,0 @@
({
"world":File (path: "models/World.glb"),
"world_dynamic":File (path: "models/World_dynamic.glb"),
"models": Folder (
path: "models/library",
),
})

Binary file not shown.

View File

@ -0,0 +1,5 @@
(
assets:
[
]
)

Binary file not shown.

View File

@ -0,0 +1,5 @@
(
assets:
[
]
)

Binary file not shown.

View File

@ -0,0 +1,9 @@
(
assets:
[
("Pillar", File ( path: "blueprints/Pillar.glb" )),
("Stone", File ( path: "materials/Stone.glb" )),
("Mover", File ( path: "blueprints/Mover.glb" )),
("Material.001", File ( path: "materials/Material.001.glb" )),
]
)

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,11 @@
use std::any::TypeId; use std::any::TypeId;
use bevy::{prelude::*, utils::hashbrown::HashSet}; use bevy::{prelude::*, utils::hashbrown::HashSet};
use blenvy::{AddToGameWorld, BlenvyPlugin, BluePrintBundle, BlueprintInfo, DynamicBlueprintInstance, GameWorldTag, HideUntilReady, SpawnBlueprint}; use blenvy::{AddToGameWorld, BlenvyPlugin, BluePrintBundle, BlueprintInfo, Dynamic, DynamicBlueprintInstance, GameWorldTag, HideUntilReady, SaveRequest, SpawnBlueprint};
use rand::Rng; use rand::Rng;
mod core; // mod game;
use crate::core::*; // use game::*;
mod game;
use game::*;
mod component_examples; mod component_examples;
use component_examples::*; use component_examples::*;
@ -37,13 +34,12 @@ fn main() {
..Default::default() ..Default::default()
}, },
// our custom plugins // our custom plugins
CorePlugin, // reusable plugins // GamePlugin, // specific to our game
GamePlugin, // specific to our game
ComponentsExamplesPlugin, // Showcases different type of components /structs ComponentsExamplesPlugin, // Showcases different type of components /structs
)) ))
.add_systems(Startup, setup_game) .add_systems(Startup, setup_game)
.add_systems(Update, (spawn_blueprint_instance, save_game, load_game)) .add_systems(Update, (spawn_blueprint_instance, move_movers, save_game, load_game))
.run(); .run();
} }
@ -60,6 +56,13 @@ fn setup_game(
HideUntilReady, // only reveal the level once it is ready HideUntilReady, // only reveal the level once it is ready
GameWorldTag, GameWorldTag,
)); ));
// here we spawn our game world/level, which is also a blueprint !
commands.spawn((
BlueprintInfo::from_path("levels/World_dynamic.glb"), // all we need is a Blueprint info...
SpawnBlueprint, // and spawnblueprint to tell blenvy to spawn the blueprint now
HideUntilReady, // only reveal the level once it is ready
));
} }
// you can also spawn blueprint instances at runtime // you can also spawn blueprint instances at runtime
@ -90,15 +93,47 @@ fn spawn_blueprint_instance(
} }
} }
fn save_game( fn move_movers(
keycode: Res<ButtonInput<KeyCode>>, mut movers: Query<(&mut Transform), With<Dynamic>>
) { ) {
if keycode.just_pressed(KeyCode::KeyS) { for mut transform in movers.iter_mut(){
println!("moving dynamic entity");
transform.translation.x += 0.01;
} }
} }
fn save_game(
keycode: Res<ButtonInput<KeyCode>>,
mut save_requests: EventWriter<SaveRequest>,
) {
if keycode.just_pressed(KeyCode::KeyS) {
save_requests.send(SaveRequest {
path: "scenes/save.scn.ron".into(),
});
}
}
/*
pub fn request_save(
mut save_requests: EventWriter<SaveRequest>,
keycode: Res<ButtonInput<KeyCode>>,
current_state: Res<State<GameState>>,
mut next_game_state: ResMut<NextState<GameState>>,
) {
if keycode.just_pressed(KeyCode::KeyS)
&& (current_state.get() != &GameState::InLoading)
&& (current_state.get() != &GameState::InSaving)
{
next_game_state.set(GameState::InSaving);
save_requests.send(SaveRequest {
path: "save.scn.ron".into(),
});
}
}*/
fn load_game( fn load_game(
keycode: Res<ButtonInput<KeyCode>>, keycode: Res<ButtonInput<KeyCode>>,
) { ) {

View File

@ -1,12 +1,14 @@
import bpy import bpy
from ...bevy_components.components.metadata import get_bevy_component_value_by_long_name
# checks if an object is dynamic # checks if an object is dynamic
# TODO: for efficiency, it might make sense to write this flag semi automatically at the root level of the object so we can skip the inner loop # TODO: for efficiency, it might make sense to write this flag semi automatically at the root level of the object so we can skip the inner loop
# TODO: we need to recompute these on blueprint changes too # TODO: we need to recompute these on blueprint changes too
# even better, keep a list of dynamic objects per scene , updated only when needed ? # even better, keep a list of dynamic objects per scene , updated only when needed ?
def is_object_dynamic(object): def is_object_dynamic(object):
is_dynamic = object['Dynamic'] if 'Dynamic' in object else False is_dynamic = get_bevy_component_value_by_long_name(object, 'blenvy::save_load::Dynamic') is not None
#is_dynamic = object['Dynamic'] if 'Dynamic' in object else False
# only look for data in the original collection if it is not alread marked as dynamic at instance level # only look for data in the original collection if it is not alread marked as dynamic at instance level
if not is_dynamic and object.type == 'EMPTY' and hasattr(object, 'instance_collection') and object.instance_collection is not None : if not is_dynamic and object.type == 'EMPTY' and hasattr(object, 'instance_collection') and object.instance_collection is not None :
#print("collection", object.instance_collection, "object", object.name) #print("collection", object.instance_collection, "object", object.name)
@ -14,15 +16,18 @@ def is_object_dynamic(object):
collection_name = object.instance_collection.name collection_name = object.instance_collection.name
original_collection = bpy.data.collections[collection_name] original_collection = bpy.data.collections[collection_name]
is_dynamic = get_bevy_component_value_by_long_name(original_collection, 'blenvy::save_load::Dynamic') is not None
# scan original collection, look for a 'Dynamic' flag # scan original collection, look for a 'Dynamic' flag
for object in original_collection.objects: """for object in original_collection.objects:
#print(" inner", object) #print(" inner", object)
if object.type == 'EMPTY' and object.name.endswith("components"): if object.type == 'EMPTY': #and object.name.endswith("components"):
for component_name in object.keys(): for component_name in object.keys():
#print(" compo", component_name) #print(" compo", component_name)
if component_name == 'Dynamic': if component_name == 'Dynamic':
is_dynamic = True is_dynamic = True
break break"""
print("IS OBJECT DYNAMIC", object, is_dynamic)
return is_dynamic return is_dynamic
def is_object_static(object): def is_object_static(object):

View File

@ -1,10 +1,8 @@
import os import os
import bpy import bpy
from pathlib import Path
from blenvy.core.helpers_collections import (traverse_tree) from blenvy.core.helpers_collections import (traverse_tree)
from blenvy.core.object_makers import make_cube from blenvy.core.object_makers import make_cube
from blenvy.materials.materials_helpers import add_material_info_to_objects, get_all_materials
from ..common.generate_temporary_scene_and_export import generate_temporary_scene_and_export from ..common.generate_temporary_scene_and_export import generate_temporary_scene_and_export
from ..common.export_gltf import (generate_gltf_export_settings) from ..common.export_gltf import (generate_gltf_export_settings)