diff --git a/Cargo.lock b/Cargo.lock index 966d15a5..e2827be3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -779,10 +779,20 @@ dependencies = [ [[package]] name = "bevy_gltf_blueprints" -version = "0.5.1" +version = "0.6.0" dependencies = [ "bevy", - "bevy_gltf_components 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bevy_gltf_components 0.2.0", +] + +[[package]] +name = "bevy_gltf_blueprints" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2123036f5738c3bc75607b04799c105dff8a5626c454369bcd1401b4ec0184" +dependencies = [ + "bevy", + "bevy_gltf_components 0.2.0", ] [[package]] @@ -792,7 +802,7 @@ dependencies = [ "bevy", "bevy_asset_loader", "bevy_editor_pls", - "bevy_gltf_blueprints", + "bevy_gltf_blueprints 0.6.0", "bevy_rapier3d", "rand", ] @@ -804,7 +814,7 @@ dependencies = [ "bevy", "bevy_asset_loader", "bevy_editor_pls", - "bevy_gltf_blueprints", + "bevy_gltf_blueprints 0.6.0", "bevy_rapier3d", "rand", ] @@ -816,7 +826,7 @@ dependencies = [ "bevy", "bevy_asset_loader", "bevy_editor_pls", - "bevy_gltf_blueprints", + "bevy_gltf_blueprints 0.6.0", "bevy_rapier3d", "rand", ] @@ -828,7 +838,7 @@ dependencies = [ "bevy", "bevy_asset_loader", "bevy_editor_pls", - "bevy_gltf_blueprints", + "bevy_gltf_blueprints 0.6.0", "bevy_rapier3d", "rand", ] @@ -840,7 +850,7 @@ dependencies = [ "bevy", "bevy_asset_loader", "bevy_editor_pls", - "bevy_gltf_blueprints", + "bevy_gltf_blueprints 0.6.0", "bevy_xpbd_3d", "rand", ] @@ -852,7 +862,7 @@ dependencies = [ "bevy", "bevy_asset_loader", "bevy_editor_pls", - "bevy_gltf_blueprints", + "bevy_gltf_blueprints 0.6.0", "bevy_rapier3d", "rand", ] @@ -864,7 +874,7 @@ dependencies = [ "bevy", "bevy_asset_loader", "bevy_editor_pls", - "bevy_gltf_blueprints", + "bevy_gltf_blueprints 0.6.0", "bevy_rapier3d", "rand", ] @@ -876,7 +886,7 @@ dependencies = [ "bevy", "bevy_asset_loader", "bevy_editor_pls", - "bevy_gltf_blueprints", + "bevy_gltf_blueprints 0.6.0", "bevy_rapier3d", "rand", ] @@ -888,7 +898,7 @@ dependencies = [ "bevy", "bevy_asset_loader", "bevy_editor_pls", - "bevy_gltf_blueprints", + "bevy_gltf_blueprints 0.6.0", "bevy_rapier3d", "rand", ] @@ -896,6 +906,8 @@ dependencies = [ [[package]] name = "bevy_gltf_components" version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd745a2988c631286404e12d184d4a30a634fbbba1deceaaa1ca7fcbc607cc7a" dependencies = [ "bevy", "ron", @@ -904,9 +916,7 @@ dependencies = [ [[package]] name = "bevy_gltf_components" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd745a2988c631286404e12d184d4a30a634fbbba1deceaaa1ca7fcbc607cc7a" +version = "0.2.1" dependencies = [ "bevy", "ron", @@ -919,7 +929,7 @@ version = "0.3.0" dependencies = [ "bevy", "bevy_editor_pls", - "bevy_gltf_components 0.2.0", + "bevy_gltf_components 0.2.1", "bevy_rapier3d", ] @@ -929,10 +939,33 @@ version = "0.3.0" dependencies = [ "bevy", "bevy_editor_pls", - "bevy_gltf_components 0.2.0", + "bevy_gltf_components 0.2.1", "bevy_rapier3d", ] +[[package]] +name = "bevy_gltf_save_load" +version = "0.1.0" +dependencies = [ + "bevy", + "bevy_gltf_blueprints 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bevy_gltf_save_load_basic_example" +version = "0.3.0" +dependencies = [ + "bevy", + "bevy_asset_loader", + "bevy_editor_pls", + "bevy_gltf_blueprints 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bevy_gltf_save_load", + "bevy_rapier3d", + "rand", + "serde", + "serde_json", +] + [[package]] name = "bevy_hierarchy" version = "0.12.1" diff --git a/Cargo.toml b/Cargo.toml index b029158b..77216a37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "crates/bevy_gltf_components", "crates/bevy_gltf_blueprints", + "crates/bevy_gltf_save_load", "examples/bevy_gltf_components/basic/", "examples/bevy_gltf_components/basic_wasm/", "examples/bevy_gltf_blueprints/basic/", @@ -12,7 +13,9 @@ members = [ "examples/bevy_gltf_blueprints/animation/", "examples/bevy_gltf_blueprints/multiple_levels/", "examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles", - "examples/bevy_gltf_blueprints/materials/" + "examples/bevy_gltf_blueprints/materials/", + "examples/bevy_gltf_save_load/basic/", + ] resolver = "2" diff --git a/README.md b/README.md index d317efd4..ed274698 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,10 @@ There is a [video tutorial/explanation](https://youtu.be/CgyNtwgYwdM) for this o The examples for the crate are [here](./examples/bevy_gltf_blueprints/) > Note: this is the recomended crate to use and uses ```bevy_gltf_components``` under the hood +- [bevy_gltf_save_load](./crates/bevy_gltf_save_load/) This crate adds the ability to save & load your game state in a relatively simple way, by leveraging the blueprint functionality of +bevy_gltf_blueprints to only save a minimal subset of dynamic data, seperating dynamic & static parts of levels etc. +The examples for the crate are [here](./examples/bevy_gltf_save_load/) +> Note: this uses ```bevy_gltf_blueprints``` under the hood ## Tools diff --git a/crates/bevy_gltf_blueprints/Cargo.toml b/crates/bevy_gltf_blueprints/Cargo.toml index 23478bcf..59387c9b 100644 --- a/crates/bevy_gltf_blueprints/Cargo.toml +++ b/crates/bevy_gltf_blueprints/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_gltf_blueprints" -version = "0.5.1" +version = "0.6.0" authors = ["Mark 'kaosat-dev' Moissette"] description = "Adds the ability to define Blueprints/Prefabs for [Bevy](https://bevyengine.org/) inside gltf files and spawn them in Bevy." homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow" diff --git a/crates/bevy_gltf_blueprints/README.md b/crates/bevy_gltf_blueprints/README.md index fe8c36d0..48ac9c1e 100644 --- a/crates/bevy_gltf_blueprints/README.md +++ b/crates/bevy_gltf_blueprints/README.md @@ -26,7 +26,7 @@ Here's a minimal usage example: # Cargo.toml [dependencies] bevy="0.12" -bevy_gltf_blueprints = { version = "0.5"} +bevy_gltf_blueprints = { version = "0.6"} ``` @@ -64,7 +64,7 @@ fn spawn_blueprint( Add the following to your `[dependencies]` section in `Cargo.toml`: ```toml -bevy_gltf_blueprints = "0.5" +bevy_gltf_blueprints = "0.6" ``` Or use `cargo add`: @@ -97,8 +97,7 @@ use bevy_gltf_blueprints::*; fn main() { App::new() - .add_plugins(DefaultPlugins) - .add_plugin( + .add_plugins(( BlueprintsPlugin{ library_folder: "advanced/models/library".into() // replace this with your blueprints library path , relative to the assets folder, format: GltfFormat::GLB,// optional, use either format: GltfFormat::GLB, or format: GltfFormat::GLTF, or ..Default::default() if you want to keep the default .glb extension, this sets what extensions/ gltf files will be looked for by the library @@ -107,7 +106,7 @@ fn main() { material_library_folder: "materials".into() //defaults to "materials" the folder to look for for the material files ..Default::default() } - ) + )) .run(); } @@ -120,7 +119,8 @@ You can spawn entities from blueprints like this: commands.spawn(( BlueprintName("Health_Pickup".to_string()), // mandatory !! SpawnHere, // mandatory !! - TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), // VERY important !! + + TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), // optional // any other component you want to insert )) @@ -168,11 +168,51 @@ commands.spawn(( There is also a bundle for convenience , which just has * a ```BlueprintName``` component * a ```SpawnHere``` component - * a ```TransformBundle``` sub-bundle (so we know where to spawn) [```BluePrintBundle```](./src/lib.rs#22) +## Additional information + +- When a blueprint is spawned, all its children entities (and nested children etc) also have an ```InBlueprint``` component that gets insert +- In cases where that is undesirable, you can add a ```NoInBlueprint``` component on the entity you spawn the blueprint with, and the components above will not be add +- if you want to overwrite the **path** where this crate looks for blueprints (gltf files) , you can add a ```Library``` component , and that will be used instead of the default path +ie : + +```rust no_run +commands + .spawn(( + Name::from("test"), + BluePrintBundle { + blueprint: BlueprintName("TestBlueprint".to_string()), + ..Default::default() + }, + Library("models".into()) // now the path to the blueprint above will be /assets/models/TestBlueprint.glb + )) +``` +- this crate also provides a special optional ```GameWorldTag``` component: this is useful when you want to keep all your spawned entities inside a root entity + +You can use it in your queries to add your entities as children of this "world" +This way all your levels, your dynamic entities etc, are kept seperated from UI nodes & other entities that are not relevant to the game world + +> Note: you should only have a SINGLE entity tagged with that component ! + +```rust no_run + commands.spawn(( + SceneBundle { + scene: models + .get(game_assets.world.id()) + .expect("main level should have been loaded") + .scenes[0] + .clone(), + ..default() + }, + bevy::prelude::Name::from("world"), + GameWorldTag, // here it is + )); +``` + + ## SystemSet the ordering of systems is very important ! @@ -239,9 +279,9 @@ pub fn animation_change_on_proximity_foxes( } ``` -see https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/animation for how to set it up correctly +see https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/animation for how to set it up correctly -particularly from https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/animation/game/in_game.rs#86 +particularly from https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/animation/game/in_game.rs#86 onward @@ -263,27 +303,27 @@ material_library_folder: "materials".into() //defaults to "materials" the folder ```bevy_gltf_blueprints``` currently does NOT take care of loading those at runtime -see https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/materials for how to set it up correctly +see https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/materials for how to set it up correctly Generating optimised blueprints and material libraries can be automated using the latests version of the [Blender plugin](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/tools/gltf_auto_export) ## Examples -https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/basic +https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/basic -https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/basic_xpbd_physics +https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/basic_xpbd_physics -https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/basic_scene_components +https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/basic_scene_components -https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/animation +https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/animation -https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/multiple_levels +https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/multiple_levels -https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/materials +https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/materials -https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/nested_blueprints +https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/nested_blueprints -https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/multiple_levels_multiple_blendfiles +https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles ## Compatible Bevy versions @@ -293,7 +333,7 @@ The main branch is compatible with the latest Bevy release, while the branch `be Compatibility of `bevy_gltf_blueprints` versions: | `bevy_gltf_blueprints` | `bevy` | | :-- | :-- | -| `0.3 - 0.5` | `0.12` | +| `0.3 - 0.6` | `0.12` | | `0.1 - 0.2` | `0.11` | | branch `main` | `0.12` | | branch `bevy_main` | `main` | diff --git a/crates/bevy_gltf_blueprints/src/aabb.rs b/crates/bevy_gltf_blueprints/src/aabb.rs index 68cca60d..ab3a21f4 100644 --- a/crates/bevy_gltf_blueprints/src/aabb.rs +++ b/crates/bevy_gltf_blueprints/src/aabb.rs @@ -1,13 +1,10 @@ use bevy::{math::Vec3A, prelude::*, render::primitives::Aabb}; -use crate::{BluePrintsConfig, BlueprintName, SpawnedRoot}; +use crate::{BluePrintsConfig, Spawned}; /// helper system that computes the compound aabbs of the scenes/blueprints pub fn compute_scene_aabbs( - root_entities: Query< - (Entity, &Name, &Children, &BlueprintName), - (With, Without), - >, + root_entities: Query<(Entity, &Name), (With, Without)>, children: Query<&Children>, existing_aabbs: Query<&Aabb>, @@ -15,10 +12,8 @@ pub fn compute_scene_aabbs( mut commands: Commands, ) { // compute compound aabb - for root_entity in root_entities.iter() { - let name = &root_entity.3 .0; - - let root_entity = root_entity.2.first().unwrap(); + for (root_entity, name) in root_entities.iter() { + // info!("generating aabb for {:?}", name); // only recompute aabb if it has not already been done before if blueprints_config.aabb_cache.contains_key(&name.to_string()) { @@ -26,10 +21,10 @@ pub fn compute_scene_aabbs( .aabb_cache .get(&name.to_string()) .expect("we should have the aabb available"); - commands.entity(*root_entity).insert(*aabb); + commands.entity(root_entity).insert(*aabb); } else { - let aabb = compute_descendant_aabb(*root_entity, &children, &existing_aabbs); - commands.entity(*root_entity).insert(aabb); + let aabb = compute_descendant_aabb(root_entity, &children, &existing_aabbs); + commands.entity(root_entity).insert(aabb); blueprints_config.aabb_cache.insert(name.to_string(), aabb); } } diff --git a/crates/bevy_gltf_blueprints/src/clone_entity.rs b/crates/bevy_gltf_blueprints/src/clone_entity.rs deleted file mode 100644 index 07e94d80..00000000 --- a/crates/bevy_gltf_blueprints/src/clone_entity.rs +++ /dev/null @@ -1,70 +0,0 @@ -use bevy::ecs::system::Command; -use bevy::prelude::*; - -// modified version from https://github.com/bevyengine/bevy/issues/1515, -// more specifically https://gist.github.com/nwtnni/85d6b87ae75337a522166c500c9a8418 -// to work with Bevy 0.11 -pub struct CloneEntity { - pub source: Entity, - pub destination: Entity, -} - -impl CloneEntity { - // Copy all components from an entity to another. - // Using an entity with no components as the destination creates a copy of the source entity. - // Panics if: - // - the components are not registered in the type registry, - // - the world does not have a type registry - // - the source or destination entity do not exist - fn clone_entity(self, world: &mut World) { - let components = { - let registry = world - .get_resource::() - .expect("the world should have a type registry") - .read(); - - world - .get_entity(self.source) - .expect("source entity should exist") - .archetype() - .components() - .map(|component_id| { - let component_info = world - .components() - .get_info(component_id) - .expect("component info should be available"); - - let type_id = component_info.type_id().unwrap(); - let type_id = registry.get(type_id).expect( - format!( - "cannot clone entity: component: {:?} is not registered", - component_info.name() - ) - .as_str(), - ); - return type_id.data::().unwrap().clone(); - }) - .collect::>() - }; - - for component in components { - let source = component - .reflect(world.get_entity(self.source).unwrap()) - .unwrap() - .clone_value(); - - let mut destination = world - .get_entity_mut(self.destination) - .expect("destination entity should exist"); - - component.apply_or_insert(&mut destination, &*source); - } - } -} - -// This allows the command to be used in systems -impl Command for CloneEntity { - fn apply(self, world: &mut World) { - self.clone_entity(world) - } -} diff --git a/crates/bevy_gltf_blueprints/src/copy_components.rs b/crates/bevy_gltf_blueprints/src/copy_components.rs new file mode 100644 index 00000000..bb241701 --- /dev/null +++ b/crates/bevy_gltf_blueprints/src/copy_components.rs @@ -0,0 +1,109 @@ +use bevy::ecs::system::Command; +use bevy::prelude::*; +use std::any::TypeId; + +// originally based https://github.com/bevyengine/bevy/issues/1515, +// more specifically https://gist.github.com/nwtnni/85d6b87ae75337a522166c500c9a8418 +// to work with Bevy 0.11 +// to copy components between entities but NOT overwriting any existing components +// plus some bells & whistles +pub struct CopyComponents { + pub source: Entity, + pub destination: Entity, + pub exclude: Vec, + pub stringent: bool, +} + +impl CopyComponents { + // Copy all components from an entity to another. + // Using an entity with no components as the destination creates a copy of the source entity. + // Panics if: + // - the components are not registered in the type registry, + // - the world does not have a type registry + // - the source or destination entity do not exist + fn transfer_components(self, world: &mut World) { + let components = { + let registry = world + .get_resource::() + .expect("the world should have a type registry") + .read(); + + world + .get_entity(self.source) + .expect("source entity should exist") + .archetype() + .components() + .filter_map(|component_id| { + let component_info = world + .components() + .get_info(component_id) + .expect("component info should be available"); + + let type_id = component_info.type_id().unwrap(); + if self.exclude.contains(&type_id) { + debug!("excluding component: {:?}", component_info.name()); + return None; + } else { + debug!( + "cloning: component: {:?} {:?}", + component_info.name(), + type_id + ); + + if let Some(type_registration) = registry.get(type_id) { + return Some(type_registration); + } else { + if self.stringent { + return Some( + registry.get(type_id).expect( + format!( + "cannot clone entity: component: {:?} is not registered", + component_info.name() + ) + .as_str(), + ), + ); + } else { + warn!( + "cannot clone component: component: {:?} is not registered", + component_info.name() + ); + return None; + } + } + } + }) + .map(|type_id| { + return ( + type_id.data::().unwrap().clone(), + type_id.type_info().type_id().clone(), // we need the original type_id down the line + ); + }) + .collect::>() + }; + + for (component, type_id) in components { + let source = component + .reflect(world.get_entity(self.source).unwrap()) + .unwrap() + .clone_value(); + + let mut destination = world + .get_entity_mut(self.destination) + .expect("destination entity should exist"); + + // println!("contains typeid {:?} {}", type_id, destination.contains_type_id(type_id)); + // we only want to copy components that are NOT already in the destination (ie no overwriting existing components) + if !destination.contains_type_id(type_id) { + component.insert(&mut destination, &*source); + } + } + } +} + +// This allows the command to be used in systems +impl Command for CopyComponents { + fn apply(self, world: &mut World) { + self.transfer_components(world) + } +} diff --git a/crates/bevy_gltf_blueprints/src/lib.rs b/crates/bevy_gltf_blueprints/src/lib.rs index 70d9419b..e335437f 100644 --- a/crates/bevy_gltf_blueprints/src/lib.rs +++ b/crates/bevy_gltf_blueprints/src/lib.rs @@ -13,8 +13,8 @@ pub use aabb::*; pub mod materials; pub use materials::*; -pub mod clone_entity; -pub use clone_entity::*; +pub mod copy_components; +pub use copy_components::*; use core::fmt; use std::path::PathBuf; @@ -33,14 +33,12 @@ pub enum GltfBlueprintsSet { pub struct BluePrintBundle { pub blueprint: BlueprintName, pub spawn_here: SpawnHere, - pub transform: TransformBundle, } impl Default for BluePrintBundle { fn default() -> Self { BluePrintBundle { blueprint: BlueprintName("default".into()), spawn_here: SpawnHere, - transform: TransformBundle::default(), } } } @@ -140,6 +138,7 @@ impl Plugin for BlueprintsPlugin { spawn_from_blueprints, compute_scene_aabbs.run_if(aabbs_enabled), apply_deferred.run_if(aabbs_enabled), + apply_deferred, materials_inject.run_if(materials_library_enabled), ) .chain() @@ -147,12 +146,7 @@ impl Plugin for BlueprintsPlugin { ) .add_systems( Update, - ( - update_spawned_root_first_child, - apply_deferred, - cleanup_scene_instances, - apply_deferred, - ) + (spawned_blueprint_post_process, apply_deferred) .chain() .in_set(GltfBlueprintsSet::AfterSpawn), ); diff --git a/crates/bevy_gltf_blueprints/src/spawn_from_blueprints.rs b/crates/bevy_gltf_blueprints/src/spawn_from_blueprints.rs index 0baaaa03..57ca64b5 100644 --- a/crates/bevy_gltf_blueprints/src/spawn_from_blueprints.rs +++ b/crates/bevy_gltf_blueprints/src/spawn_from_blueprints.rs @@ -1,4 +1,4 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; use bevy::{gltf::Gltf, prelude::*}; @@ -19,44 +19,87 @@ pub struct BlueprintName(pub String); pub struct SpawnHere; #[derive(Component)] -/// FlagComponent for spawned entity +/// FlagComponent for dynamically spawned scenes pub struct Spawned; -#[derive(Component)] -/// helper component, just to transfer some data -pub(crate) struct Original(pub Entity); +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +/// flag component marking any spwaned child of blueprints ..unless the original entity was marked with the 'NoInBlueprint' marker component +pub struct InBlueprint; + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +/// flag component preventing any spwaned child of blueprints to be marked with the InBlueprint component +pub struct NoInBlueprint; + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +// this allows overriding the default library path for a given entity/blueprint +pub struct Library(pub PathBuf); + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +/// flag component to force adding newly spawned entity as child of game world +pub struct AddToGameWorld; #[derive(Component)] -/// FlagComponent for dynamically spawned scenes -pub struct SpawnedRoot; +/// helper component, just to transfer child data +pub(crate) struct OriginalChildren(pub Vec); /// main spawning functions, /// * also takes into account the already exisiting "override" components, ie "override components" > components from blueprint pub(crate) fn spawn_from_blueprints( spawn_placeholders: Query< - (Entity, &Name, &BlueprintName, &Transform, Option<&Parent>), ( - Added, - Added, - Without, - Without, + Entity, + &BlueprintName, + Option<&Transform>, + Option<&Parent>, + Option<&Library>, + Option<&AddToGameWorld>, + Option<&Name>, ), + (Added, Added, Without), >, mut commands: Commands, - mut game_world: Query<(Entity, &Children), With>, + mut game_world: Query>, assets_gltf: Res>, asset_server: Res, blueprints_config: Res, + + children: Query<&Children>, ) { - for (entity, name, blupeprint_name, transform, original_parent) in spawn_placeholders.iter() { - debug!("need to spawn {:?}, id: {:?}", blupeprint_name.0, entity); + for ( + entity, + blupeprint_name, + transform, + original_parent, + library_override, + add_to_world, + name, + ) in spawn_placeholders.iter() + { + debug!( + "need to spawn {:?} for entity {:?}, id: {:?}, parent:{:?}", + blupeprint_name.0, name, entity, original_parent + ); + + let mut original_children: Vec = vec![]; + if let Ok(c) = children.get(entity) { + for child in c.iter() { + original_children.push(*child); + } + } let what = &blupeprint_name.0; let model_file_name = format!("{}.{}", &what, &blueprints_config.format); - let model_path = - Path::new(&blueprints_config.library_folder).join(Path::new(model_file_name.as_str())); + + // library path is either defined at the plugin level or overriden by optional Library components + let library_path = + library_override.map_or_else(|| &blueprints_config.library_folder, |l| &l.0); + let model_path = Path::new(&library_path).join(Path::new(model_file_name.as_str())); debug!("attempting to spawn {:?}", model_path); let model_handle: Handle = asset_server.load(model_path); @@ -73,32 +116,30 @@ pub(crate) fn spawn_from_blueprints( .expect("there should be at least one named scene in the gltf file to spawn"); let scene = &gltf.named_scenes[main_scene_name]; - let child_scene = commands - .spawn(( - SceneBundle { - scene: scene.clone(), - transform: transform.clone(), - ..Default::default() - }, - name.clone(), - // Parent(world) // FIXME/ would be good if this worked directly - SpawnedRoot, - BlueprintName(blupeprint_name.0.clone()), - Original(entity), - Animations { - named_animations: gltf.named_animations.clone(), - }, - )) - .id(); - - let world = game_world.single_mut(); - let mut parent = world.1[0]; // FIXME: dangerous hack because our gltf data have a single child like this, but might not always be the case - - // ideally, insert the newly created entity as a child of the original parent, if any, the world otherwise - if let Some(original_parent) = original_parent { - parent = original_parent.get(); + // transforms are optional, but still deal with them correctly + let mut transforms: Transform = Transform::default(); + if transform.is_some() { + transforms = transform.unwrap().clone(); } - commands.entity(parent).add_child(child_scene); + commands.entity(entity).insert(( + SceneBundle { + scene: scene.clone(), + transform: transforms, + ..Default::default() + }, + Animations { + named_animations: gltf.named_animations.clone(), + }, + Spawned, + OriginalChildren(original_children), + )); + + if add_to_world.is_some() { + let world = game_world + .get_single_mut() + .expect("there should be a game world present"); + commands.entity(world).add_child(entity); + } } } diff --git a/crates/bevy_gltf_blueprints/src/spawn_post_process.rs b/crates/bevy_gltf_blueprints/src/spawn_post_process.rs index a2aa6ea8..2c3f3fcb 100644 --- a/crates/bevy_gltf_blueprints/src/spawn_post_process.rs +++ b/crates/bevy_gltf_blueprints/src/spawn_post_process.rs @@ -1,128 +1,94 @@ +use std::any::TypeId; + use bevy::prelude::*; +use bevy::scene::SceneInstance; +// use bevy::utils::hashbrown::HashSet; use super::{AnimationPlayerLink, Animations}; -use super::{CloneEntity, SpawnHere}; -use super::{Original, SpawnedRoot}; - -#[derive(Component)] -/// FlagComponent for dynamically spawned scenes -pub(crate) struct SpawnedRootProcessed; - -/// this system updates the first (and normally only) child of a scene flaged SpawnedRoot -/// - adds a name based on parent component (spawned scene) which is named on the scene name/prefab to be instanciated -// FIXME: updating hierarchy does not work in all cases ! this is sadly dependant on the structure of the exported blend data -// - blender root-> object with properties => WORKS -// - scene instance -> does not work -// it might be due to how we add components to the PARENT item in gltf to components -pub(crate) fn update_spawned_root_first_child( - // +use super::{SpawnHere, Spawned}; +use crate::{CopyComponents, InBlueprint, NoInBlueprint, OriginalChildren}; + +/// this system is in charge of doing any necessary post processing after a blueprint scene has been spawned +/// - 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 children of the blueprint scene into the original entity +/// - it add AnimationLink components so that animations can be controlled from the original entity +/// - it cleans up/ removes a few , by then uneeded components +pub(crate) fn spawned_blueprint_post_process( unprocessed_entities: Query< - (Entity, &Children, &Name, &Parent, &Original), - (With, Without), + ( + Entity, + &Children, + &OriginalChildren, + &Animations, + Option<&NoInBlueprint>, + Option<&Name>, + ), + (With, With, With), >, - mut commands: Commands, - - animations: Query<&Animations>, added_animation_players: Query<(Entity, &Parent), Added>, -) { - /* - currently we have - - scene instance - - root node ? - - the actual stuff - we want to remove the root node - so we wend up with - - scene instance - - the actual stuff - - so - - get root node - - add its children to the scene instance - - remove root node - - Another issue is, the scene instance become empty if we have a pickabke as the "actual stuff", so that would lead to a lot of - empty scenes if we spawn pickables - - perhaps another system that cleans up empty scene instances ? + all_children: Query<&Children>, - FIME: this is all highly dependent on the hierachy ;.. - */ + mut commands: Commands, +) { + for (original, children, original_children, animations, no_inblueprint, name) in + unprocessed_entities.iter() + { + debug!("post processing blueprint for entity {:?}", name); - for (scene_instance, children, name, parent, original) in unprocessed_entities.iter() { - // if children.len() == 0 { warn!("timing issue ! no children found, please restart your bevy app (bug being investigated)"); - // println!("children of scene {:?}", children); continue; } // the root node is the first & normally only child inside a scene, it is the one that has all relevant components - let root_entity = children.first().unwrap(); //FIXME: and what about childless ones ?? => should not be possible normally - // let root_entity_data = all_children.get(*root_entity).unwrap(); - - // fixme : randomization should be controlled via parameters, perhaps even the seed could be specified ? - // use this https://rust-random.github.io/book/guide-seeding.html#a-simple-number, blenders seeds are also uInts - // also this is not something we want every time, this should be a settable parameter when requesting a spawn - - // add missing name of entity, based on the wrapper's name - let name = name.clone(); - - // this is our new actual entity - commands.entity(*root_entity).insert(( - bevy::prelude::Name::from(name.clone()), - // ItemType {name}, - )); - - // flag the spawned_root as being processed - commands.entity(scene_instance).insert(SpawnedRootProcessed); - - // parent is either the world or an entity with a marker (BlueprintName) - commands.entity(parent.get()).add_child(*root_entity); - - let matching_animations = animations.get(scene_instance); + let mut root_entity = Entity::PLACEHOLDER; //FIXME: and what about childless ones ?? => should not be possible normally + // let diff = HashSet::from_iter(original_children.0).difference(HashSet::from_iter(children)); + // we find the first child that was not in the entity before (aka added during the scene spawning) + for c in children.iter() { + if !original_children.0.contains(c) { + root_entity = *c; + break; + } + } - if let Ok(animations) = matching_animations { - if animations.named_animations.keys().len() > 0 { - for (added, parent) in added_animation_players.iter() { - if parent.get() == *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(*root_entity) - .insert(AnimationPlayerLink(added)); - commands.entity(*root_entity).insert(Animations { - named_animations: animations.named_animations.clone(), - }); - } - } + // 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(root_entity) { + commands.entity(child).insert(InBlueprint); } } - commands.add(CloneEntity { - source: original.0, - destination: *root_entity, + // copy components into from blueprint instance's root_entity to original entity + commands.add(CopyComponents { + source: root_entity, + destination: original, + exclude: vec![TypeId::of::(), TypeId::of::()], + stringent: false, }); - // remove the original entity, now that we have cloned it into the spawned scenes first child - commands.entity(original.0).despawn_recursive(); - commands.entity(*root_entity).remove::(); - } -} + // we move all of children of the blueprint instance one level to the original entity + if let Ok(root_entity_children) = all_children.get(root_entity) { + for child in root_entity_children.iter() { + // info!("copying child {:?} upward from {:?} to {:?}", names.get(*child), root_entity, original); + commands.entity(original).add_child(*child); + } + } -/// cleans up dynamically spawned scenes so that they get despawned if they have no more children -pub(crate) fn cleanup_scene_instances( - scene_instances: Query<(Entity, &Children), With>, - without_children: Query, Without)>, // if there are not children left, bevy removes Children ? - mut commands: Commands, -) { - for (entity, children) in scene_instances.iter() { - if children.len() == 0 { - // it seems this does not happen ? - debug!("cleaning up emptied spawned scene instance"); - commands.entity(entity).despawn_recursive(); + if animations.named_animations.keys().len() > 0 { + for (added, parent) in added_animation_players.iter() { + if parent.get() == 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(AnimationPlayerLink(added)); + } + } } - } - for entity in without_children.iter() { - debug!("cleaning up emptied spawned scene instance"); - commands.entity(entity).despawn_recursive(); + + commands.entity(original).remove::(); + commands.entity(original).remove::(); + commands.entity(original).remove::>(); + commands.entity(root_entity).despawn_recursive(); } } diff --git a/crates/bevy_gltf_components/Cargo.toml b/crates/bevy_gltf_components/Cargo.toml index d7ba1224..c0b08fb3 100644 --- a/crates/bevy_gltf_components/Cargo.toml +++ b/crates/bevy_gltf_components/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_gltf_components" -version = "0.2.0" +version = "0.2.1" authors = ["Mark 'kaosat-dev' Moissette"] description = "Allows you to define [Bevy](https://bevyengine.org/) components direclty inside gltf files and instanciate the components on the Bevy side." homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow" diff --git a/crates/bevy_gltf_components/src/gltf_to_components.rs b/crates/bevy_gltf_components/src/gltf_to_components.rs index 82c774eb..cf319f6d 100644 --- a/crates/bevy_gltf_components/src/gltf_to_components.rs +++ b/crates/bevy_gltf_components/src/gltf_to_components.rs @@ -10,7 +10,7 @@ use bevy::reflect::{Reflect, TypeInfo, TypeRegistry}; use bevy::scene::Scene; use bevy::utils::HashMap; use bevy::{ - log::{debug, info, warn}, + log::{debug, warn}, prelude::{Assets, Name, Parent, ResMut}, }; @@ -252,5 +252,5 @@ pub fn gltf_extras_to_components( } } } - info!("done injecting components from gltf_extras /n"); + debug!("done injecting components from gltf_extras"); } diff --git a/crates/bevy_gltf_save_load/Cargo.toml b/crates/bevy_gltf_save_load/Cargo.toml new file mode 100644 index 00000000..c41ab0e3 --- /dev/null +++ b/crates/bevy_gltf_save_load/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "bevy_gltf_save_load" +version = "0.1.0" +authors = ["Mark 'kaosat-dev' Moissette"] +description = "Save & load your bevy games" +homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow" +repository = "https://github.com/kaosat-dev/Blender_bevy_components_workflow" +keywords = ["gamedev", "bevy", "save", "load", "serialize"] +categories = ["game-development"] +edition = "2021" +license = "MIT OR Apache-2.0" + +[dev-dependencies] +bevy = { version = "0.12", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf"] } + +[dependencies] +bevy = { version = "0.12", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf"] } +bevy_gltf_blueprints = "0.6" diff --git a/crates/bevy_gltf_save_load/LICENSE.md b/crates/bevy_gltf_save_load/LICENSE.md new file mode 100644 index 00000000..ad21aacd --- /dev/null +++ b/crates/bevy_gltf_save_load/LICENSE.md @@ -0,0 +1,4 @@ +This crate is available under either: + +* The [MIT License](./LICENSE_MIT) +* The [Apache License, Version 2.0](./LICENSE_APACHE) \ No newline at end of file diff --git a/crates/bevy_gltf_save_load/LICENSE_APACHE.md b/crates/bevy_gltf_save_load/LICENSE_APACHE.md new file mode 100644 index 00000000..f7489773 --- /dev/null +++ b/crates/bevy_gltf_save_load/LICENSE_APACHE.md @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2023] [Mark "kaosat-dev" Moissette] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/crates/bevy_gltf_save_load/LICENSE_MIT.md b/crates/bevy_gltf_save_load/LICENSE_MIT.md new file mode 100644 index 00000000..f8b90945 --- /dev/null +++ b/crates/bevy_gltf_save_load/LICENSE_MIT.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Mark "kaosat-dev" Moissette + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/crates/bevy_gltf_save_load/README.md b/crates/bevy_gltf_save_load/README.md new file mode 100644 index 00000000..e68daac8 --- /dev/null +++ b/crates/bevy_gltf_save_load/README.md @@ -0,0 +1,312 @@ +[![Crates.io](https://img.shields.io/crates/v/bevy_gltf_save_load)](https://crates.io/crates/bevy_gltf_save_load) +[![Docs](https://img.shields.io/docsrs/bevy_gltf_save_load)](https://docs.rs/bevy_gltf_save_load/latest/bevy_gltf_save_load/) +[![License](https://img.shields.io/crates/l/bevy_gltf_save_load)](https://github.com/kaosat-dev/Blender_bevy_components_workflow/blob/main/crates/bevy_gltf_save_load/License.md) +[![Bevy tracking](https://img.shields.io/badge/Bevy%20tracking-released%20version-lightblue)](https://github.com/bevyengine/bevy/blob/main/docs/plugins_guidelines.md#main-branch-tracking) + +# bevy_gltf_save_load + +Built upon [bevy_gltf_blueprints](https://crates.io/crates/bevy_gltf_blueprints) this crate adds the ability to easilly **save** and **load** your game worlds for [Bevy](https://bevyengine.org/) . + +* leverages blueprints & seperation between + * **dynamic** entities : entities that can change during the lifetime of your app/game + * **static** entities : entities that do NOT change (typically, a part of your levels/ environements) +* and allows allow for : + * a simple save/load workflow thanks to the above + * ability to specify **which entities** to save or to exclude + * ability to specify **which components** to save or to exclude + * ability to specify **which resources** to save or to exclude + * small(er) save files (only a portion of the entities is saved) + +Particularly useful when using [Blender](https://www.blender.org/) as an editor for the [Bevy](https://bevyengine.org/) game engine, combined with the [Blender plugin](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/tools/gltf_auto_export) that does a lot of the work for you (including spliting generating seperate gltf files for your static vs dynamic assets) + + +A bit of heads up: + +* very opinionated ! +* still in the early stages & not 100% feature complete +* fun fact: as the static level structure is stored seperatly, you can change your level layout & **still** reload an existing save file + + +## Usage + +Here's a minimal usage example: + +```toml +# Cargo.toml +[dependencies] +bevy="0.12" +bevy_gltf_save_load = "0.1" +bevy_gltf_blueprints = "0.6" // also needed +``` + +```rust no_run +use bevy::prelude::*; +use bevy_gltf_save_load::*; + +fn main() { + App::new() + .add_plugins(( + DefaultPlugins, + SaveLoadPlugin::default() + )) + .run(); +} + + + +// add a system to trigger saving +pub fn request_save( + mut save_requests: EventWriter, + keycode: Res>, +) +{ + if keycode.just_pressed(KeyCode::S) { + save_requests.send(SaveRequest { + path: "save.scn.ron".into(), + }) + } +} + +// add a system to trigger loading +pub fn request_load( + mut load_requests: EventWriter, + keycode: Res>, +) +{ + if keycode.just_pressed(KeyCode::L) { + save_requests.send(LoadRequest { + path: "save.scn.ron".into(), + }) + } +} + +// setting up your world +// on initial setup, the static entities & the dynamic entities are kept seperate for clarity & loaded as blueprints from 2 seperate files +pub fn setup_game( + mut commands: Commands, + mut next_game_state: ResMut>, +) { + info!("setting up game world"); + // here we actually spawn our game world/level + let world_root = commands + .spawn(( + Name::from("world"), + GameWorldTag, + InAppRunning, + TransformBundle::default(), + InheritedVisibility::default(), + )) + .id(); + + // and we fill it with static entities + let static_data = commands + .spawn(( + Name::from("static"), + BluePrintBundle { + blueprint: BlueprintName("World".to_string()), + ..Default::default() + }, + StaticEntitiesRoot, + Library("models".into()) + )) + .id(); + + // and we fill it with dynamic entities + let dynamic_data = commands + .spawn(( + Name::from("dynamic"), + BluePrintBundle { + blueprint: BlueprintName("World_dynamic".to_string()), + ..Default::default() + }, + DynamicEntitiesRoot, + NoInBlueprint, + Library("models".into()) + )) + .id(); + commands.entity(world_root).add_child(static_data); + commands.entity(world_root).add_child(dynamic_data); + + next_game_state.set(GameState::InGame) +} + + +``` + +take a look at the [example]('https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_save_load/basic/src/game/mod.rs) for more clarity + + +## Installation + +Add the following to your `[dependencies]` section in `Cargo.toml`: + +```toml +bevy_gltf_save_load = "0.1" +bevy_gltf_blueprints = "0.6" // also needed, as bevy_gltf_save_load does not re-export it at this time + +``` + +Or use `cargo add`: + +```toml +cargo add bevy_gltf_save_load +``` + +## Setup + +```rust no_run +use bevy::prelude::*; +use bevy_gltf_save_load::*; + +fn main() { + App::new() + .add_plugins(( + DefaultPlugins + SaveLoadPlugin::default() + )) + .run(); +} + +``` + +you likely need to configure your settings (otherwise, not much will be saved) + +```rust no_run +use bevy::prelude::*; +use bevy_gltf_save_load::*; + +fn main() { + App::new() + .add_plugins(( + DefaultPlugins, + SaveLoadPlugin { + save_path: "scenes".into(), // where do we save files to (under assets for now) defaults to "scenes" + component_filter: SceneFilter::Allowlist(HashSet::from([ // this is using Bevy's build in SceneFilter, you can compose what components you want to allow/deny + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + // and any other commponent you want to include/exclude + ])), + resource_filter: SceneFilter::deny_all(), // same logic as above, but for resources : also be careful & remember to register your resources ! + ..Default::default() + }, + // you need to configure the blueprints plugin as well (might be pre_configured in the future, but for now you need to do it manually) + BlueprintsPlugin { + library_folder: "models/library".into(), + format: GltfFormat::GLB, + aabbs: true, + ..Default::default() + }, + )) + .run(); +} + +``` +### How to make sure your entites will be saved + +- only entites that have a **Dynamic** component will be saved ! (the component is provided as part of the crate) +- you can either add that component at runtime or have it baked-in in the Blueprint + +### Component Filter: + +- by default only the following components are going to be saved + - **Parent** + - **Children** + - **BlueprintName** : part of bevy_gltf_blueprints, used under the hood + - **SpawnHere** :part of bevy_gltf_blueprints, used under the hood + - **Dynamic** : included in this crate, allows you to tag components as dynamic aka saveable ! Use this to make sure your entities are saved ! + +- you **CANNOT** remove these as they are part of the boilerplate +- you **CAN** add however many other components you want, allow them all etc as you see fit +- you can find more information about the SceneFilter object [here](https://bevyengine.org/news/bevy-0-11/#scene-filtering) and [here](https://docs.rs/bevy/latest/bevy/scene/enum.SceneFilter.html) + + +## Events + + +- to trigger **saving** use the ```SaveRequest``` event +```rust no_run +// add a system to trigger saving +pub fn request_save( + mut save_requests: EventWriter, + keycode: Res>, +) +{ + if keycode.just_pressed(KeyCode::S) { + save_requests.send(SaveRequest { + path: "save.scn.ron".into(), + }) + } +} + +``` + + +- to trigger **loading** use the ```LoadRequest``` event + +```rust no_run +// add a system to trigger saving +pub fn request_load( + mut load_requests: EventWriter, + keycode: Res>, +) +{ + if keycode.just_pressed(KeyCode::L) { + save_requests.send(LoadRequest { + path: "save.scn.ron".into(), + }) + } +} +``` + +- you also notified when saving / loading is done + - ```SavingFinished``` for saving + - ```LoadingFinished``` for loading + +> Note: I **highly** recomend you change states when you start/finish saving & loading, otherwise things **will** get unpredictable +Please see [the example]('https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_save_load/basic/src/game/mod.rs#77') for this. + +## Additional notes + +- the name + path of the **static** level blueprint/gltf file will be saved as part of the save file, and reused to dynamically +load the correct static assets, which is necessary when you have multiple levels, and thus all required information to reload a save is contained within the save + +## SystemSet + +For convenience ```bevy_gltf_save_load``` provides two **SystemSets** + - [```LoadingSet```](./src/lib.rs#19) + - [```SavingSet```](./src/lib.rs#24) + + +## Examples + +Highly advised to get a better understanding of how things work ! +To get started I recomend looking at + +- [world setup]('https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_save_load/basic/src/game/in_game.rs#13') +- [various events & co]('https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_save_load/basic/src/game/mod.rs#77') + + +All examples are here: + +- https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_save_load/basic + + +## Compatible Bevy versions + +The main branch is compatible with the latest Bevy release, while the branch `bevy_main` tries to track the `main` branch of Bevy (PRs updating the tracked commit are welcome). + +Compatibility of `bevy_gltf_save_load` versions: +| `bevy_gltf_save_load` | `bevy` | +| :-- | :-- | +| `0.1 ` | `0.12` | +| branch `main` | `0.12` | +| branch `bevy_main` | `main` | + + +## License + +This crate, all its code, contents & assets is Dual-licensed under either of + +- Apache License, Version 2.0, ([LICENSE-APACHE](./LICENSE_APACHE.md) or https://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](./LICENSE_MIT.md) or https://opensource.org/licenses/MIT) \ No newline at end of file diff --git a/crates/bevy_gltf_save_load/src/gltf_out_test.rs b/crates/bevy_gltf_save_load/src/gltf_out_test.rs new file mode 100644 index 00000000..e47b1faa --- /dev/null +++ b/crates/bevy_gltf_save_load/src/gltf_out_test.rs @@ -0,0 +1,216 @@ +use gltf_json as json; +use json::camera::Type; +use json::validation::{Checked, Validate}; +use serde_json::value::{to_raw_value, RawValue}; +use serde::Serialize; +use bevy::reflect::TypeRegistryArc; + + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +enum Output { + /// Output standard glTF. + Standard, + + /// Output binary glTF. + Binary, +} +#[derive(Serialize)] +struct MyExtraData { + a: u32, + b: u32, + BlueprintName: String, + SpawnHere: String, +} + +/* +pub fn serialize_gltf_inner(serialize: S) -> Result +where + S: Serialize, +{ + let pretty_config = ron::ser::PrettyConfig::default() + .indentor(" ".to_string()) + .new_line("\n".to_string()); + ron::ser::to_string_pretty(&serialize, pretty_config) +}*/ + +pub fn serialize_gltf(scene:&DynamicScene, registry: &TypeRegistryArc) { + +} + +pub fn save_game( + world: &mut World, +) { + + let mut save_path:String = "".into(); + let mut events = world + .resource_mut::>(); + for event in events.get_reader().read(&events) { + info!("SAVE EVENT !! {:?}", event); + save_path = event.path.clone(); + } + info!("SAVING TO {}", save_path); + events.clear(); + + let saveable_entities: Vec = world + .query_filtered::>() + .iter(world) + .collect(); + + debug!("saveable entities {}", saveable_entities.len()); + + let components = HashSet::from([ + TypeId::of::(), + TypeId::of::(), + TypeId::of::() , + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + + + + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + + TypeId::of::(), + + + + ]); + + let filter = SceneFilter::Allowlist(components); + + + let mut scene_builder = DynamicSceneBuilder::from_world(world).with_filter(filter); + + let dyn_scene = scene_builder + + + /* .allow::() + .allow::() + .allow::()*/ + + /* .deny::() + .deny::() + .deny::() + .deny::() + .deny::() + .deny::() + .deny::() + .deny::() + .deny::() + // camera stuff + .deny::() + .deny::() + .deny::() + .deny::() + .deny::() + .deny::() + //.deny::() + */ + .extract_entities(saveable_entities.into_iter()) + .build(); + + let serialized_scene = dyn_scene + .serialize_ron(world.resource::()) + .unwrap(); + + let mut root = gltf_json::Root::default(); + + // unfortunatly, not available yet + /*let node = root.push(json::Node { + //mesh: Some(mesh), + ..Default::default() + }); + + root.push(json::Scene { + extensions: Default::default(), + extras: Default::default(), + name: None, + nodes: vec![node], + });*/ + + + + + + let camera = json::camera::Perspective{ + aspect_ratio: Some(0.5), + yfov: 32.0, + zfar: Some(30.), + znear: 0.0, + extensions: None, + extras: None + }; + /*let camera = json::Camera{ + name:Some("Camera".into()), + orthographic: None, + perspective:None, + extensions: None, + extras: None, + type_: Checked, + };*/ + let gna = to_raw_value(&MyExtraData { a: 1, b: 2, BlueprintName: "Foo".into(), SpawnHere:"".into() }).unwrap() ; + let node = json::Node { + camera: None,//Some(camera), + children: None, + extensions: None, + extras: Some(gna), + matrix: None, + mesh:None, + name: Some("yeah".into()), + rotation: None, + scale: None, + translation: Some([0.5, 10.0 ,-100.]), + skin: None, + weights: None + // mesh: Some(json::Index::new(0)), + //..Default::default() + }; + + let root = json::Root { + accessors: vec![], //[positions, colors], + buffers: vec![], + buffer_views: vec![], + meshes: vec![], + nodes: vec![node], + scenes: vec![json::Scene { + extensions: Default::default(), + extras: Default::default(), + name: Some("Foo".to_string()), + nodes: vec![json::Index::new(0)], + }], + ..Default::default() + }; + + + + + let gltf_save_name = "test.gltf"; + let writer = fs::File::create(format!("assets/scenes/{gltf_save_name}") ).expect("I/O error"); + json::serialize::to_writer_pretty(writer, &root).expect("Serialization error"); + + // let bin = to_padded_byte_vector(triangle_vertices); + // let mut writer = fs::File::create("triangle/buffer0.bin").expect("I/O error"); + // writer.write_all(&bin).expect("I/O error"); + + + #[cfg(not(target_arch = "wasm32"))] + IoTaskPool::get() + .spawn(async move { + // Write the scene RON data to file + File::create(format!("assets/scenes/{save_path}")) + .and_then(|mut file| file.write(serialized_scene.as_bytes())) + .expect("Error while writing scene to file"); + + + + }) + .detach(); +} \ No newline at end of file diff --git a/crates/bevy_gltf_save_load/src/lib.rs b/crates/bevy_gltf_save_load/src/lib.rs new file mode 100644 index 00000000..00c45b9f --- /dev/null +++ b/crates/bevy_gltf_save_load/src/lib.rs @@ -0,0 +1,108 @@ +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 bevy_gltf_blueprints::GltfBlueprintsSet; + +#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] +pub enum SavingSet { + Save, +} + +#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] +pub enum LoadingSet { + Load, +} + +// Plugin configuration + +#[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"), + } + } +} + +#[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::() + .register_type::() + // TODO: remove these in bevy 0.13, as these are now registered by default + .register_type::() + .register_type::() + .register_type::() + .add_event::() + .add_event::() + .add_event::() + .add_event::() + .insert_resource(SaveLoadConfig { + save_path: self.save_path.clone(), + + component_filter: self.component_filter.clone(), + resource_filter: self.resource_filter.clone(), + }) + .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::()) + .run_if(not(resource_exists::())) + .in_set(LoadingSet::Load), + ) + .add_systems( + Update, + (load_static, apply_deferred, cleanup_loaded_scene) + .chain() + .run_if(resource_exists::()) + // .run_if(in_state(AppState::LoadingGame)) + .in_set(LoadingSet::Load), + ); + } +} diff --git a/crates/bevy_gltf_save_load/src/loading.rs b/crates/bevy_gltf_save_load/src/loading.rs new file mode 100644 index 00000000..9342ecf2 --- /dev/null +++ b/crates/bevy_gltf_save_load/src/loading.rs @@ -0,0 +1,156 @@ +use bevy::{prelude::*, scene::SceneInstance}; +use bevy_gltf_blueprints::{BluePrintBundle, BlueprintName, GameWorldTag, Library}; +use std::path::Path; + +use crate::{DynamicEntitiesRoot, SaveLoadConfig, StaticEntitiesRoot, StaticEntitiesStorage}; + +#[derive(Event)] +pub struct LoadRequest { + pub path: String, +} + +#[derive(Event)] +pub struct LoadingFinished; + +#[derive(Resource, Default)] +pub struct LoadRequested { + pub path: String, +} + +#[derive(Resource, Default)] +pub(crate) struct LoadFirstStageDone; + +#[derive(Component, Reflect, Debug, Default)] +#[reflect(Component)] +pub(crate) struct CleanupScene; + +/// helper system that "converts" loadRequest events to LoadRequested resources +pub(crate) fn mark_load_requested( + mut load_requests: EventReader, + mut commands: Commands, +) { + let mut save_path: String = "".into(); + for load_request in load_requests.read() { + if load_request.path != "" { + save_path = load_request.path.clone(); + } + } + if save_path != "" { + commands.insert_resource(LoadRequested { path: save_path }); + } +} + +// TODO: replace with generic despawner ? +pub(crate) fn unload_world(mut commands: Commands, gameworlds: Query>) { + for e in gameworlds.iter() { + info!("--loading: despawn old world/level"); + commands.entity(e).despawn_recursive(); + } +} + +pub(crate) fn load_game( + mut commands: Commands, + asset_server: Res, + load_request: Res, + save_load_config: Res, +) { + info!("--loading: load dynamic data"); + let save_path = load_request.path.clone(); + let save_path = Path::new(&save_load_config.save_path).join(Path::new(save_path.as_str())); + + info!("LOADING FROM {:?}", save_path); + + let world_root = commands + .spawn(( + bevy::prelude::Name::from("world"), + GameWorldTag, + TransformBundle::default(), + InheritedVisibility::default(), + )) + .id(); + + // and we fill it with dynamic data + // let input = std::fs::read(&path)?; + let dynamic_data = commands + .spawn(( + DynamicSceneBundle { + scene: asset_server.load(save_path), + ..default() + }, + bevy::prelude::Name::from("dynamic"), + DynamicEntitiesRoot, + )) + .id(); + + // commands.entity(world_root).add_child(static_data); + commands.entity(world_root).add_child(dynamic_data); + + commands.insert_resource(LoadFirstStageDone); + + info!("--loading: loaded dynamic data"); +} + +pub(crate) fn load_static( + dynamic_worlds: Query>, + world_root: Query>, + mut commands: Commands, + mut loading_finished: EventWriter, + + static_entities: Option>, +) { + if let Some(info) = static_entities { + info!("--loading static data {:?}", info.name); + let static_data = commands + .spawn(( + Name::from("static"), + BluePrintBundle { + blueprint: BlueprintName(info.name.clone()), + ..Default::default() + }, + StaticEntitiesRoot, + )) + .id(); + + if info.library_path != "" { + commands + .entity(static_data) + .insert(Library(info.library_path.clone().into())); + } + + let world_root = world_root.get_single().unwrap(); + commands.entity(world_root).add_child(static_data); + + info!("--loading: loaded static data"); + for entity in dynamic_worlds.iter() { + commands.entity(entity).insert( + CleanupScene, // we mark this scene as needing a cleanup + ); + } + + loading_finished.send(LoadingFinished); + } +} + +pub(crate) fn cleanup_loaded_scene( + loaded_scenes: Query< + Entity, + ( + Added, + With, + With, + ), + >, + mut commands: Commands, +) { + for loaded_scene in loaded_scenes.iter() { + info!("REMOVING DynamicScene"); + commands + .entity(loaded_scene) + .remove::>() + .remove::() + .remove::(); + + commands.remove_resource::(); + commands.remove_resource::(); + } +} diff --git a/crates/bevy_gltf_save_load/src/saveable.rs b/crates/bevy_gltf_save_load/src/saveable.rs new file mode 100644 index 00000000..b2a40321 --- /dev/null +++ b/crates/bevy_gltf_save_load/src/saveable.rs @@ -0,0 +1,6 @@ +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); diff --git a/crates/bevy_gltf_save_load/src/saving.rs b/crates/bevy_gltf_save_load/src/saving.rs new file mode 100644 index 00000000..51aeb0c1 --- /dev/null +++ b/crates/bevy_gltf_save_load/src/saving.rs @@ -0,0 +1,192 @@ +use bevy::prelude::*; +use bevy::tasks::IoTaskPool; +use bevy_gltf_blueprints::{BlueprintName, InBlueprint, Library, SpawnHere}; + +use std::fs::File; +use std::io::Write; +use std::path::Path; + +use crate::{Dynamic, DynamicEntitiesRoot, SaveLoadConfig, StaticEntitiesRoot}; + +#[derive(Event, Debug)] +pub struct SaveRequest { + pub path: String, +} + +#[derive(Event)] +pub struct SavingFinished; + +pub fn should_save(save_requests: EventReader) -> bool { + return save_requests.len() > 0; +} + +#[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, With)>, + root_entities: Query, Without)>>, // With + dynamic_entities: Query<(Entity, &Parent, Option<&Children>), With>, + static_entities: Query<(Entity, &BlueprintName, Option<&Library>), With>, + + 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::>(); + + for event in events.get_reader().read(&events) { + info!("SAVE EVENT !! {:?}", event); + save_path = event.path.clone(); + } + events.clear(); + + let saveable_entities: Vec = world + .query_filtered::, Without, Without)>() + .iter(world) + .collect(); + + let saveable_root_entities: Vec = world + .query_filtered::, Without, With)>() + .iter(world) + .collect(); + + info!("saveable entities {}", saveable_entities.len()); + info!("saveable root entities {}", saveable_root_entities.len()); + + let save_load_config = world + .get_resource::() + .expect("SaveLoadConfig should exist at this stage"); + + // we hardcode some of the always allowed types + let filter = save_load_config + .component_filter + .clone() + .allow::() + .allow::() + .allow::() + .allow::() + .allow::(); + + // for root entities, it is the same EXCEPT we make sure parents are not included + let filter_root = filter.clone().deny::(); + + let filter_resources = save_load_config + .resource_filter + .clone() + .allow::(); + + // 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()); + + 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_ron(world.resource::()) + .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, + mut commands: Commands, +) { + for (entity, original_parent) in needs_parent_reset.iter() { + commands.entity(original_parent.0).add_child(entity); + } + commands.remove_resource::(); + 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); + } +}*/ diff --git a/examples/bevy_gltf_blueprints/animation/src/game/in_game.rs b/examples/bevy_gltf_blueprints/animation/src/game/in_game.rs index 19c3f169..f29efcf8 100644 --- a/examples/bevy_gltf_blueprints/animation/src/game/in_game.rs +++ b/examples/bevy_gltf_blueprints/animation/src/game/in_game.rs @@ -72,13 +72,12 @@ pub fn spawn_test( .spawn(( BluePrintBundle { blueprint: BlueprintName("Fox".to_string()), - transform: TransformBundle::from_transform(Transform::from_xyz(x, 0.0, y)), ..Default::default() }, bevy::prelude::Name::from(format!("Spawned{}", name_index)), // BlueprintName("Health_Pickup".to_string()), // SpawnHere, - // TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), + TransformBundle::from_transform(Transform::from_xyz(x, 0.0, y)), Velocity { linvel: Vec3::new(vel_x, vel_y, vel_z), angvel: Vec3::new(0.0, 0.0, 0.0), diff --git a/examples/bevy_gltf_blueprints/basic/src/core/mod.rs b/examples/bevy_gltf_blueprints/basic/src/core/mod.rs index f9145633..9c38fa77 100644 --- a/examples/bevy_gltf_blueprints/basic/src/core/mod.rs +++ b/examples/bevy_gltf_blueprints/basic/src/core/mod.rs @@ -10,9 +10,6 @@ pub use relationships::*; pub mod physics; pub use physics::*; -// pub mod save_load; -// pub use save_load::*; - use bevy::prelude::*; use bevy_gltf_blueprints::*; @@ -23,7 +20,6 @@ impl Plugin for CorePlugin { LightingPlugin, CameraPlugin, PhysicsPlugin, - // SaveLoadPlugin, BlueprintsPlugin { library_folder: "models/library".into(), format: GltfFormat::GLB, diff --git a/examples/bevy_gltf_blueprints/basic/src/core/save_load/loading.rs b/examples/bevy_gltf_blueprints/basic/src/core/save_load/loading.rs deleted file mode 100644 index 73ef523d..00000000 --- a/examples/bevy_gltf_blueprints/basic/src/core/save_load/loading.rs +++ /dev/null @@ -1,218 +0,0 @@ -use bevy::prelude::*; -use bevy_gltf_blueprints::{clone_entity::CloneEntity, GameWorldTag, SpawnHere}; - -use crate::{ - assets::GameAssets, - state::{AppState, GameState, InAppRunning}, -}; - -use super::Saveable; - -const SCENE_FILE_PATH: &str = "scenes/save.scn.ron"; - -#[derive(Component, Debug)] -pub struct TempLoadedSceneMarker; - -#[derive(Component, Debug)] -pub struct SaveablesToRemove(Vec<(Entity, Name)>); - -#[derive(Component, Event)] -pub struct LoadRequest { - pub path: String, -} - -pub fn should_load(save_requested_events: EventReader) -> bool { - return save_requested_events.len() > 0; -} - -pub fn load_prepare( - mut next_app_state: ResMut>, - mut next_game_state: ResMut>, -) { - next_app_state.set(AppState::LoadingGame); - next_game_state.set(GameState::None); - info!("--loading: prepare") -} - -/// unload the level recursively -pub fn _unload_world_old(world: &mut World) { - let entities: Vec = world - // .query_filtered::, With)>>() - .query_filtered::>() // our level/world contains this component - .iter(world) - .collect(); - for entity in entities { - // Check the entity again in case it was despawned recursively - if world.get_entity(entity).is_some() { - world.entity_mut(entity).despawn_recursive(); - } - } -} - -pub fn unload_world(mut commands: Commands, gameworlds: Query>) { - for e in gameworlds.iter() { - info!("--loading: despawn old world/level"); - commands.entity(e).despawn_recursive(); - } -} - -// almost identical to setup_game, !!?? -pub fn load_world( - mut commands: Commands, - game_assets: Res, - // scenes: ResMut, -) { - info!("--loading: loading world/level"); - - commands.spawn(( - SceneBundle { - scene: game_assets.world.clone(), - ..default() - }, - bevy::prelude::Name::from("world"), - GameWorldTag, - InAppRunning, - )); -} - -pub fn load_saved_scene(mut commands: Commands, asset_server: Res) { - commands.spawn(( - DynamicSceneBundle { - // Scenes are loaded just like any other asset. - scene: asset_server.load(SCENE_FILE_PATH), - ..default() - }, - TempLoadedSceneMarker, - )); - // commands.entity(world).add_child(child_scene); - info!("--loading: loaded saved scene"); -} - -pub fn process_loaded_scene( - loaded_scene: Query<(Entity, &Children), With>, - named_entities: Query<(Entity, &Name, &Parent)>, // FIXME: very inneficient - mut commands: Commands, - - mut game_world: Query<(Entity, &Children), With>, - saveables: Query<(Entity, &Name), With>, - asset_server: Res, -) { - for (loaded_scene, children) in loaded_scene.iter() { - info!("--loading: post processing loaded scene"); - - let mut entities_to_load: Vec<(Entity, Name)> = vec![]; - - for loaded_entity in children.iter() { - if let Ok((source, name, _)) = named_entities.get(*loaded_entity) { - entities_to_load.push((source, name.clone())); - - let mut found = false; - for (e, n, p) in named_entities.iter() { - // if we have an entity with the same name as in same file, overwrite - if e != source && name.as_str() == n.as_str() { - // println!("found entity with same name {} {} {:?} {:?}", name, n, source, e); - // source is entity within the newly loaded scene (source), e is within the existing world (destination) - info!("copying data from {:?} to {:?}", source, e); - commands.add(CloneEntity { - source: source, - destination: e, - }); - // FIXME: issue with hierarchy & parenting, would be nice to be able to filter out components from CloneEntity - commands.entity(p.get()).add_child(e); - commands.entity(source).despawn_recursive(); - found = true; - break; - } - } - // entity not found in the list of existing entities (ie entities that came as part of the level) - // so we spawn a new one - if !found { - info!("generating new entity"); - let world = game_world.single_mut(); - let world = world.1[0]; - - let new_entity = commands - .spawn((bevy::prelude::Name::from(name.clone()), SpawnHere)) - .id(); - - commands.add(CloneEntity { - source: source, - destination: new_entity, - }); - - commands.entity(world).add_child(new_entity); - info!("copying data from {:?} to {:?}", source, new_entity); - } - } - } - commands.spawn(SaveablesToRemove(entities_to_load.clone())); - - // if an entity is present in the world but NOT in the saved entities , it should be removed from the world - // ideally this should be run between spawning of the world/level AND spawn_placeholders - - // remove the dynamic scene - info!("--loading: DESPAWNING LOADED SCENE"); - commands.entity(loaded_scene).despawn_recursive(); - - asset_server.mark_unused_assets(); - asset_server.free_unused_assets(); - } - //for saveable in saveables.iter(){ - // println!("SAVEABLE BEFORE {:?}", saveable) - //} -} - -pub fn final_cleanup( - saveables_to_remove: Query<(Entity, &SaveablesToRemove)>, - mut commands: Commands, - saveables: Query<(Entity, &Name), With>, - mut next_app_state: ResMut>, - mut next_game_state: ResMut>, -) { - if let Ok((e, entities_to_load)) = saveables_to_remove.get_single() { - info!("saveables to remove {:?}", entities_to_load); - for (e, n) in saveables.iter() { - let mut found = false; - println!("SAVEABLE {}", n); - - //let entities_to_load = saveables_to_remove.single(); - for (en, na) in entities_to_load.0.iter() { - found = na.as_str() == n.as_str(); - if found { - break; - } - } - if !found { - println!("REMOVING THIS ONE {}", n); - commands.entity(e).despawn_recursive(); - } - } - // if there is a saveable that is NOT in the list of entities to load, despawn it - - // despawn list - commands.entity(e).despawn_recursive(); - - info!("--loading: done, move to InGame state"); - // next_app_state.set(AppState::AppRunning); - next_game_state.set(GameState::InGame); - } -} - -fn process_loaded_scene_load_alt( - entities: Query<(Entity, &Children), With>, - named_entities: Query<(Entity, &Name, &Parent)>, // FIXME: very inneficient - mut commands: Commands, -) { - for (entity, children) in entities.iter() { - let mut entities_to_load: Vec<(Entity, Name)> = vec![]; - for saved_source in children.iter() { - if let Ok((source, name, _)) = named_entities.get(*saved_source) { - println!("AAAAAAA {}", name); - entities_to_load.push((source, name.clone())); - } - } - println!("entities to load {:?}", entities_to_load); - - commands.entity(entity).despawn_recursive(); - } -} diff --git a/examples/bevy_gltf_blueprints/basic/src/core/save_load/mod.rs b/examples/bevy_gltf_blueprints/basic/src/core/save_load/mod.rs deleted file mode 100644 index 3d0e91ed..00000000 --- a/examples/bevy_gltf_blueprints/basic/src/core/save_load/mod.rs +++ /dev/null @@ -1,70 +0,0 @@ -pub mod saveable; -use bevy::asset::free_unused_assets_system; -use bevy_gltf_components::GltfComponentsSet; -pub use saveable::*; - -pub mod saving; -pub use saving::*; - -pub mod loading; -pub use loading::*; - -use bevy::prelude::*; -use bevy::prelude::{App, IntoSystemConfigs, Plugin}; -use bevy::utils::Uuid; - -use bevy_gltf_blueprints::GltfBlueprintsSet; - -#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] -pub enum LoadingSet { - Load, - PostLoad, -} - -pub struct SaveLoadPlugin; -impl Plugin for SaveLoadPlugin { - fn build(&self, app: &mut App) { - app - .register_type::() - .register_type::() - .add_event::() - .add_event::() - - .configure_sets( - Update, - (LoadingSet::Load, LoadingSet::PostLoad) - .chain() - .before(GltfBlueprintsSet::Spawn) - .before(GltfComponentsSet::Injection) - ) - - .add_systems(PreUpdate, save_game.run_if(should_save)) - - .add_systems(Update, - ( - load_prepare, - unload_world, - load_world, - load_saved_scene, - // process_loaded_scene - ) - .chain() - .run_if(should_load) // .run_if(in_state(AppState::AppRunning)) - .in_set(LoadingSet::Load) - ) - .add_systems(Update, - ( - process_loaded_scene, - apply_deferred, - final_cleanup, - apply_deferred, - free_unused_assets_system - ) - .chain() - .in_set(LoadingSet::PostLoad) - ) - - // .add_systems(Update, bla) - ; - } -} diff --git a/examples/bevy_gltf_blueprints/basic/src/core/save_load/old.rs b/examples/bevy_gltf_blueprints/basic/src/core/save_load/old.rs deleted file mode 100644 index 7d8a3899..00000000 --- a/examples/bevy_gltf_blueprints/basic/src/core/save_load/old.rs +++ /dev/null @@ -1,137 +0,0 @@ -const NEW_SCENE_FILE_PATH:&str="save.scn.ron"; - - - - -use bevy::ecs::component::Components; -use bevy::ecs::entity::EntityMap; -use serde::{Deserialize, Serialize}; - - -use std::io::Read; -use bevy::scene::serde::SceneDeserializer; -use ron::Deserializer; -use serde::de::DeserializeSeed; - - - - -#[derive(Debug, Deserialize)] -struct Components2; - -#[derive(Debug, Deserialize)] -struct Fake { - resources: HashMap, - entities: HashMap -} - -fn ron_test(){ - let full_path = "/home/ckaos/projects/grappling-boom-bot/assets/save.ron"; - match File::open(full_path) { - Ok(mut file) => { - let mut serialized_scene = Vec::new(); - if let Err(why) = file.read_to_end(&mut serialized_scene) { - error!("file read failed: {why:?}"); - } - match Deserializer::from_bytes(&serialized_scene) { - Ok(mut deserializer) => { - // deserializer. - let bla:Fake = ron::from_str("( - resources: {}, - entities: {} - )").unwrap(); - info!("testing {:?}", bla); - info!("YOYO DONE YO !") - } - Err(why) => { - error!("deserializer creation failed: {why:?}"); - } - } - } - Err(why) => { - error!("load failed: {why:?}"); - } - } -} - -fn inject_component_data(world: &mut World, scene: DynamicScene){ - let mut entity_map = EntityMap::default(); - if let Err(why) = scene.write_to_world(world, &mut entity_map) { - panic!("world write failed: {why:?}"); - } - println!("entity map {:?}", entity_map); - // TODO: EntityMap doesn't implement `iter()` - for old_entity in entity_map.keys() { - let entity = entity_map.get(old_entity).unwrap(); - info!("entity update required: {old_entity:?} -> {entity:?}"); - let e_mut = world - .entity_mut(entity); - } - - info!("done loading scene"); -} - -fn post_load(world: &mut World){ - let full_path = "/home/ckaos/projects/grappling-boom-bot/assets/save.ron"; - match File::open(full_path) { - Ok(mut file) => { - let mut serialized_scene = Vec::new(); - if let Err(why) = file.read_to_end(&mut serialized_scene) { - error!("file read failed: {why:?}"); - } - match Deserializer::from_bytes(&serialized_scene) { - Ok(mut deserializer) => { - let result = SceneDeserializer { - type_registry: &world.resource::().read(), - } - .deserialize(&mut deserializer); - info!("deserialize done"); - match result { - Ok(scene) => { - info!("scene loaded"); - // scene.write_to_world(world, entity_map) - // println!("{:?}", scene.entities); - inject_component_data(world, scene); - /*for dyn_ent in scene.entities.iter(){ - // let mut query = scene.world.query::<(Entity, &Name, &GltfExtras, &Parent)>(); - }*/ - } - Err(why) => { - error!("deserialization failed: {why:?}"); - } - } - } - Err(why) => { - error!("deserializer creation failed: {why:?}"); - } - } - } - Err(why) => { - error!("load failed: {why:?}"); - } - } - -} - - - -#[derive(Component, Reflect, Debug, Default )] -#[reflect(Component)] -pub struct Hackish; - - - -/// unload saveables -fn unload_saveables(world: &mut World) { - let entities: Vec = world - .query_filtered::>()// our level/world contains this component - .iter(world) - .collect(); - for entity in entities { - // Check the entity again in case it was despawned recursively - if world.get_entity(entity).is_some() { - info!("despawning"); - world.entity_mut(entity).despawn_recursive(); - } - } -} \ No newline at end of file diff --git a/examples/bevy_gltf_blueprints/basic/src/core/save_load/saveable.rs b/examples/bevy_gltf_blueprints/basic/src/core/save_load/saveable.rs deleted file mode 100644 index 67a4c657..00000000 --- a/examples/bevy_gltf_blueprints/basic/src/core/save_load/saveable.rs +++ /dev/null @@ -1,14 +0,0 @@ -use bevy::prelude::*; -use bevy::utils::Uuid; - -#[derive(Component, Reflect, Debug)] -#[reflect(Component)] -pub struct Saveable { - id: Uuid, -} - -impl Default for Saveable { - fn default() -> Self { - Saveable { id: Uuid::new_v4() } - } -} diff --git a/examples/bevy_gltf_blueprints/basic/src/core/save_load/saving.rs b/examples/bevy_gltf_blueprints/basic/src/core/save_load/saving.rs deleted file mode 100644 index 46d0b1af..00000000 --- a/examples/bevy_gltf_blueprints/basic/src/core/save_load/saving.rs +++ /dev/null @@ -1,87 +0,0 @@ -use bevy::pbr::{Clusters, VisiblePointLights}; -use bevy::render::camera::CameraRenderGraph; -use bevy::render::view::VisibleEntities; -use bevy::tasks::IoTaskPool; -use bevy::{gltf::GltfExtras, prelude::*}; -use bevy_rapier3d::prelude::RigidBody; -use std::fs::File; -use std::io::Write; - -use crate::core::physics::Collider; -use crate::game::{Pickable, Player}; - -use super::Saveable; - -const NEW_SCENE_FILE_PATH: &str = "save.scn.ron"; - -#[derive(Component, Event)] -pub struct SaveRequest { - pub path: String, -} - -pub fn should_save( - // keycode: Res>, - save_requested_events: EventReader, -) -> bool { - return save_requested_events.len() > 0; - - // return keycode.just_pressed(KeyCode::S) -} - -pub fn save_game( - world: &mut World, - // save_requested_events: EventReader, -) { - info!("saving"); - // world. - /*for bli in save_requested_events.iter(){ - println!("SAAAAVE TO THISSSSS {:?}", bli.path) - }*/ - - let saveable_entities: Vec = world - .query_filtered::>() - .iter(world) - .collect(); - - /*let static_entities: Vec = world - .query_filtered::>() - .iter(world) - .collect();*/ - println!("saveable entities {}", saveable_entities.len()); - - let mut scene_builder = DynamicSceneBuilder::from_world(world); - scene_builder - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - // camera stuff - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - //.deny::() - .extract_entities(saveable_entities.into_iter()); - - let dyn_scene = scene_builder.build(); - let serialized_scene = dyn_scene - .serialize_ron(world.resource::()) - .unwrap(); - - #[cfg(not(target_arch = "wasm32"))] - IoTaskPool::get() - .spawn(async move { - // Write the scene RON data to file - File::create(format!("assets/scenes/{NEW_SCENE_FILE_PATH}")) - .and_then(|mut file| file.write(serialized_scene.as_bytes())) - .expect("Error while writing scene to file"); - }) - .detach(); -} diff --git a/examples/bevy_gltf_blueprints/basic/src/game/in_game.rs b/examples/bevy_gltf_blueprints/basic/src/game/in_game.rs index 86fa8de0..2a077f7d 100644 --- a/examples/bevy_gltf_blueprints/basic/src/game/in_game.rs +++ b/examples/bevy_gltf_blueprints/basic/src/game/in_game.rs @@ -71,13 +71,12 @@ pub fn spawn_test( .spawn(( BluePrintBundle { blueprint: BlueprintName("Health_Pickup".to_string()), - transform: TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), ..Default::default() }, bevy::prelude::Name::from(format!("test{}", name_index)), // BlueprintName("Health_Pickup".to_string()), // SpawnHere, - // TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), + TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), Velocity { linvel: Vec3::new(vel_x, vel_y, vel_z), angvel: Vec3::new(0.0, 0.0, 0.0), @@ -88,7 +87,7 @@ pub fn spawn_test( } } -pub fn spawn_test_failing( +pub fn spawn_test_unregisted_components( keycode: Res>, mut commands: Commands, @@ -115,13 +114,12 @@ pub fn spawn_test_failing( .spawn(( BluePrintBundle { blueprint: BlueprintName("Health_Pickup".to_string()), - transform: TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), ..Default::default() }, bevy::prelude::Name::from(format!("test{}", name_index)), // BlueprintName("Health_Pickup".to_string()), // SpawnHere, - // TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), + TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), Velocity { linvel: Vec3::new(vel_x, vel_y, vel_z), angvel: Vec3::new(0.0, 0.0, 0.0), diff --git a/examples/bevy_gltf_blueprints/basic/src/game/mod.rs b/examples/bevy_gltf_blueprints/basic/src/game/mod.rs index 67716de8..00689c22 100644 --- a/examples/bevy_gltf_blueprints/basic/src/game/mod.rs +++ b/examples/bevy_gltf_blueprints/basic/src/game/mod.rs @@ -104,7 +104,7 @@ impl Plugin for GamePlugin { player_move_demo, //.run_if(in_state(AppState::Running)), // test_collision_events spawn_test, - spawn_test_failing, + spawn_test_unregisted_components, ) .run_if(in_state(GameState::InGame)), ) diff --git a/examples/bevy_gltf_blueprints/basic_scene_components/src/game/in_game.rs b/examples/bevy_gltf_blueprints/basic_scene_components/src/game/in_game.rs index f98f8c33..e8090faa 100644 --- a/examples/bevy_gltf_blueprints/basic_scene_components/src/game/in_game.rs +++ b/examples/bevy_gltf_blueprints/basic_scene_components/src/game/in_game.rs @@ -63,13 +63,12 @@ pub fn spawn_test( .spawn(( BluePrintBundle { blueprint: BlueprintName("Health_Pickup".to_string()), - transform: TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), ..Default::default() }, bevy::prelude::Name::from(format!("test{}", name_index)), // BlueprintName("Health_Pickup".to_string()), // SpawnHere, - // TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), + TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), Velocity { linvel: Vec3::new(vel_x, vel_y, vel_z), angvel: Vec3::new(0.0, 0.0, 0.0), diff --git a/examples/bevy_gltf_blueprints/basic_wasm/src/assets/assets_game.rs b/examples/bevy_gltf_blueprints/basic_wasm/src/assets/assets_game.rs index c3f93d7a..066f7db9 100644 --- a/examples/bevy_gltf_blueprints/basic_wasm/src/assets/assets_game.rs +++ b/examples/bevy_gltf_blueprints/basic_wasm/src/assets/assets_game.rs @@ -8,20 +8,22 @@ pub struct GameAssets { #[asset(key = "world")] pub world: Handle, - - - - #[asset(paths("models/library/Container.glb", "models/library/Health_Pickup.glb", "models/library/MagicTeapot.glb", - "models/library/Pillar.glb", - "models/library/Player.glb", - "models/library/Unused_in_level_test.glb"), collection(typed))] + #[asset( + paths( + "models/library/Container.glb", + "models/library/Health_Pickup.glb", + "models/library/MagicTeapot.glb", + "models/library/Pillar.glb", + "models/library/Player.glb", + "models/library/Unused_in_level_test.glb" + ), + collection(typed) + )] pub models: Vec>, - - /* #[asset(key = "models", collection(typed, mapped))] pub models: HashMap>, - + #[asset(key = "models", collection(typed))] pub models: Vec>,*/ } diff --git a/examples/bevy_gltf_blueprints/basic_wasm/src/game/in_game.rs b/examples/bevy_gltf_blueprints/basic_wasm/src/game/in_game.rs index 86fa8de0..f1861ce7 100644 --- a/examples/bevy_gltf_blueprints/basic_wasm/src/game/in_game.rs +++ b/examples/bevy_gltf_blueprints/basic_wasm/src/game/in_game.rs @@ -71,13 +71,12 @@ pub fn spawn_test( .spawn(( BluePrintBundle { blueprint: BlueprintName("Health_Pickup".to_string()), - transform: TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), ..Default::default() }, bevy::prelude::Name::from(format!("test{}", name_index)), // BlueprintName("Health_Pickup".to_string()), // SpawnHere, - // TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), + TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), Velocity { linvel: Vec3::new(vel_x, vel_y, vel_z), angvel: Vec3::new(0.0, 0.0, 0.0), @@ -88,7 +87,7 @@ pub fn spawn_test( } } -pub fn spawn_test_failing( +pub fn spawn_test_unregisted_components( keycode: Res>, mut commands: Commands, @@ -115,13 +114,10 @@ pub fn spawn_test_failing( .spawn(( BluePrintBundle { blueprint: BlueprintName("Health_Pickup".to_string()), - transform: TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), ..Default::default() }, bevy::prelude::Name::from(format!("test{}", name_index)), - // BlueprintName("Health_Pickup".to_string()), - // SpawnHere, - // TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), + TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), Velocity { linvel: Vec3::new(vel_x, vel_y, vel_z), angvel: Vec3::new(0.0, 0.0, 0.0), diff --git a/examples/bevy_gltf_blueprints/basic_wasm/src/game/mod.rs b/examples/bevy_gltf_blueprints/basic_wasm/src/game/mod.rs index 67716de8..00689c22 100644 --- a/examples/bevy_gltf_blueprints/basic_wasm/src/game/mod.rs +++ b/examples/bevy_gltf_blueprints/basic_wasm/src/game/mod.rs @@ -104,7 +104,7 @@ impl Plugin for GamePlugin { player_move_demo, //.run_if(in_state(AppState::Running)), // test_collision_events spawn_test, - spawn_test_failing, + spawn_test_unregisted_components, ) .run_if(in_state(GameState::InGame)), ) diff --git a/examples/bevy_gltf_blueprints/basic_xpbd_physics/src/game/in_game.rs b/examples/bevy_gltf_blueprints/basic_xpbd_physics/src/game/in_game.rs index f87bc127..352197ba 100644 --- a/examples/bevy_gltf_blueprints/basic_xpbd_physics/src/game/in_game.rs +++ b/examples/bevy_gltf_blueprints/basic_xpbd_physics/src/game/in_game.rs @@ -69,13 +69,12 @@ pub fn spawn_test( .spawn(( BluePrintBundle { blueprint: BlueprintName("Health_Pickup".to_string()), - transform: TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), ..Default::default() }, bevy::prelude::Name::from(format!("test{}", name_index)), // BlueprintName("Health_Pickup".to_string()), // SpawnHere, - // TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), + TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), LinearVelocity(Vec3::new(vel_x, vel_y, vel_z)), AngularVelocity::ZERO, )) diff --git a/examples/bevy_gltf_blueprints/materials/src/game/in_game.rs b/examples/bevy_gltf_blueprints/materials/src/game/in_game.rs index 276e9485..579f1938 100644 --- a/examples/bevy_gltf_blueprints/materials/src/game/in_game.rs +++ b/examples/bevy_gltf_blueprints/materials/src/game/in_game.rs @@ -72,13 +72,12 @@ pub fn spawn_test( .spawn(( BluePrintBundle { blueprint: BlueprintName("Watermelon2".to_string()), - transform: TransformBundle::from_transform(Transform::from_xyz(x, 3.0, y)), ..Default::default() }, bevy::prelude::Name::from(format!("Watermelon{}", name_index)), // BlueprintName("Health_Pickup".to_string()), // SpawnHere, - // TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), + TransformBundle::from_transform(Transform::from_xyz(x, 3.0, y)), Velocity { linvel: Vec3::new(vel_x, vel_y, vel_z), angvel: Vec3::new(0.0, 0.0, 0.0), diff --git a/examples/bevy_gltf_blueprints/multiple_levels/src/core/mod.rs b/examples/bevy_gltf_blueprints/multiple_levels/src/core/mod.rs index e2771da1..3271ad45 100644 --- a/examples/bevy_gltf_blueprints/multiple_levels/src/core/mod.rs +++ b/examples/bevy_gltf_blueprints/multiple_levels/src/core/mod.rs @@ -10,9 +10,6 @@ pub use relationships::*; pub mod physics; pub use physics::*; -// pub mod save_load; -// pub use save_load::*; - use bevy::prelude::*; use bevy_gltf_blueprints::*; @@ -23,7 +20,6 @@ impl Plugin for CorePlugin { LightingPlugin, CameraPlugin, PhysicsPlugin, - // SaveLoadPlugin, BlueprintsPlugin { library_folder: "models/library".into(), ..Default::default() diff --git a/examples/bevy_gltf_blueprints/multiple_levels/src/core/save_load/loading.rs b/examples/bevy_gltf_blueprints/multiple_levels/src/core/save_load/loading.rs deleted file mode 100644 index 73ef523d..00000000 --- a/examples/bevy_gltf_blueprints/multiple_levels/src/core/save_load/loading.rs +++ /dev/null @@ -1,218 +0,0 @@ -use bevy::prelude::*; -use bevy_gltf_blueprints::{clone_entity::CloneEntity, GameWorldTag, SpawnHere}; - -use crate::{ - assets::GameAssets, - state::{AppState, GameState, InAppRunning}, -}; - -use super::Saveable; - -const SCENE_FILE_PATH: &str = "scenes/save.scn.ron"; - -#[derive(Component, Debug)] -pub struct TempLoadedSceneMarker; - -#[derive(Component, Debug)] -pub struct SaveablesToRemove(Vec<(Entity, Name)>); - -#[derive(Component, Event)] -pub struct LoadRequest { - pub path: String, -} - -pub fn should_load(save_requested_events: EventReader) -> bool { - return save_requested_events.len() > 0; -} - -pub fn load_prepare( - mut next_app_state: ResMut>, - mut next_game_state: ResMut>, -) { - next_app_state.set(AppState::LoadingGame); - next_game_state.set(GameState::None); - info!("--loading: prepare") -} - -/// unload the level recursively -pub fn _unload_world_old(world: &mut World) { - let entities: Vec = world - // .query_filtered::, With)>>() - .query_filtered::>() // our level/world contains this component - .iter(world) - .collect(); - for entity in entities { - // Check the entity again in case it was despawned recursively - if world.get_entity(entity).is_some() { - world.entity_mut(entity).despawn_recursive(); - } - } -} - -pub fn unload_world(mut commands: Commands, gameworlds: Query>) { - for e in gameworlds.iter() { - info!("--loading: despawn old world/level"); - commands.entity(e).despawn_recursive(); - } -} - -// almost identical to setup_game, !!?? -pub fn load_world( - mut commands: Commands, - game_assets: Res, - // scenes: ResMut, -) { - info!("--loading: loading world/level"); - - commands.spawn(( - SceneBundle { - scene: game_assets.world.clone(), - ..default() - }, - bevy::prelude::Name::from("world"), - GameWorldTag, - InAppRunning, - )); -} - -pub fn load_saved_scene(mut commands: Commands, asset_server: Res) { - commands.spawn(( - DynamicSceneBundle { - // Scenes are loaded just like any other asset. - scene: asset_server.load(SCENE_FILE_PATH), - ..default() - }, - TempLoadedSceneMarker, - )); - // commands.entity(world).add_child(child_scene); - info!("--loading: loaded saved scene"); -} - -pub fn process_loaded_scene( - loaded_scene: Query<(Entity, &Children), With>, - named_entities: Query<(Entity, &Name, &Parent)>, // FIXME: very inneficient - mut commands: Commands, - - mut game_world: Query<(Entity, &Children), With>, - saveables: Query<(Entity, &Name), With>, - asset_server: Res, -) { - for (loaded_scene, children) in loaded_scene.iter() { - info!("--loading: post processing loaded scene"); - - let mut entities_to_load: Vec<(Entity, Name)> = vec![]; - - for loaded_entity in children.iter() { - if let Ok((source, name, _)) = named_entities.get(*loaded_entity) { - entities_to_load.push((source, name.clone())); - - let mut found = false; - for (e, n, p) in named_entities.iter() { - // if we have an entity with the same name as in same file, overwrite - if e != source && name.as_str() == n.as_str() { - // println!("found entity with same name {} {} {:?} {:?}", name, n, source, e); - // source is entity within the newly loaded scene (source), e is within the existing world (destination) - info!("copying data from {:?} to {:?}", source, e); - commands.add(CloneEntity { - source: source, - destination: e, - }); - // FIXME: issue with hierarchy & parenting, would be nice to be able to filter out components from CloneEntity - commands.entity(p.get()).add_child(e); - commands.entity(source).despawn_recursive(); - found = true; - break; - } - } - // entity not found in the list of existing entities (ie entities that came as part of the level) - // so we spawn a new one - if !found { - info!("generating new entity"); - let world = game_world.single_mut(); - let world = world.1[0]; - - let new_entity = commands - .spawn((bevy::prelude::Name::from(name.clone()), SpawnHere)) - .id(); - - commands.add(CloneEntity { - source: source, - destination: new_entity, - }); - - commands.entity(world).add_child(new_entity); - info!("copying data from {:?} to {:?}", source, new_entity); - } - } - } - commands.spawn(SaveablesToRemove(entities_to_load.clone())); - - // if an entity is present in the world but NOT in the saved entities , it should be removed from the world - // ideally this should be run between spawning of the world/level AND spawn_placeholders - - // remove the dynamic scene - info!("--loading: DESPAWNING LOADED SCENE"); - commands.entity(loaded_scene).despawn_recursive(); - - asset_server.mark_unused_assets(); - asset_server.free_unused_assets(); - } - //for saveable in saveables.iter(){ - // println!("SAVEABLE BEFORE {:?}", saveable) - //} -} - -pub fn final_cleanup( - saveables_to_remove: Query<(Entity, &SaveablesToRemove)>, - mut commands: Commands, - saveables: Query<(Entity, &Name), With>, - mut next_app_state: ResMut>, - mut next_game_state: ResMut>, -) { - if let Ok((e, entities_to_load)) = saveables_to_remove.get_single() { - info!("saveables to remove {:?}", entities_to_load); - for (e, n) in saveables.iter() { - let mut found = false; - println!("SAVEABLE {}", n); - - //let entities_to_load = saveables_to_remove.single(); - for (en, na) in entities_to_load.0.iter() { - found = na.as_str() == n.as_str(); - if found { - break; - } - } - if !found { - println!("REMOVING THIS ONE {}", n); - commands.entity(e).despawn_recursive(); - } - } - // if there is a saveable that is NOT in the list of entities to load, despawn it - - // despawn list - commands.entity(e).despawn_recursive(); - - info!("--loading: done, move to InGame state"); - // next_app_state.set(AppState::AppRunning); - next_game_state.set(GameState::InGame); - } -} - -fn process_loaded_scene_load_alt( - entities: Query<(Entity, &Children), With>, - named_entities: Query<(Entity, &Name, &Parent)>, // FIXME: very inneficient - mut commands: Commands, -) { - for (entity, children) in entities.iter() { - let mut entities_to_load: Vec<(Entity, Name)> = vec![]; - for saved_source in children.iter() { - if let Ok((source, name, _)) = named_entities.get(*saved_source) { - println!("AAAAAAA {}", name); - entities_to_load.push((source, name.clone())); - } - } - println!("entities to load {:?}", entities_to_load); - - commands.entity(entity).despawn_recursive(); - } -} diff --git a/examples/bevy_gltf_blueprints/multiple_levels/src/core/save_load/mod.rs b/examples/bevy_gltf_blueprints/multiple_levels/src/core/save_load/mod.rs deleted file mode 100644 index 3d0e91ed..00000000 --- a/examples/bevy_gltf_blueprints/multiple_levels/src/core/save_load/mod.rs +++ /dev/null @@ -1,70 +0,0 @@ -pub mod saveable; -use bevy::asset::free_unused_assets_system; -use bevy_gltf_components::GltfComponentsSet; -pub use saveable::*; - -pub mod saving; -pub use saving::*; - -pub mod loading; -pub use loading::*; - -use bevy::prelude::*; -use bevy::prelude::{App, IntoSystemConfigs, Plugin}; -use bevy::utils::Uuid; - -use bevy_gltf_blueprints::GltfBlueprintsSet; - -#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] -pub enum LoadingSet { - Load, - PostLoad, -} - -pub struct SaveLoadPlugin; -impl Plugin for SaveLoadPlugin { - fn build(&self, app: &mut App) { - app - .register_type::() - .register_type::() - .add_event::() - .add_event::() - - .configure_sets( - Update, - (LoadingSet::Load, LoadingSet::PostLoad) - .chain() - .before(GltfBlueprintsSet::Spawn) - .before(GltfComponentsSet::Injection) - ) - - .add_systems(PreUpdate, save_game.run_if(should_save)) - - .add_systems(Update, - ( - load_prepare, - unload_world, - load_world, - load_saved_scene, - // process_loaded_scene - ) - .chain() - .run_if(should_load) // .run_if(in_state(AppState::AppRunning)) - .in_set(LoadingSet::Load) - ) - .add_systems(Update, - ( - process_loaded_scene, - apply_deferred, - final_cleanup, - apply_deferred, - free_unused_assets_system - ) - .chain() - .in_set(LoadingSet::PostLoad) - ) - - // .add_systems(Update, bla) - ; - } -} diff --git a/examples/bevy_gltf_blueprints/multiple_levels/src/core/save_load/old.rs b/examples/bevy_gltf_blueprints/multiple_levels/src/core/save_load/old.rs deleted file mode 100644 index 7d8a3899..00000000 --- a/examples/bevy_gltf_blueprints/multiple_levels/src/core/save_load/old.rs +++ /dev/null @@ -1,137 +0,0 @@ -const NEW_SCENE_FILE_PATH:&str="save.scn.ron"; - - - - -use bevy::ecs::component::Components; -use bevy::ecs::entity::EntityMap; -use serde::{Deserialize, Serialize}; - - -use std::io::Read; -use bevy::scene::serde::SceneDeserializer; -use ron::Deserializer; -use serde::de::DeserializeSeed; - - - - -#[derive(Debug, Deserialize)] -struct Components2; - -#[derive(Debug, Deserialize)] -struct Fake { - resources: HashMap, - entities: HashMap -} - -fn ron_test(){ - let full_path = "/home/ckaos/projects/grappling-boom-bot/assets/save.ron"; - match File::open(full_path) { - Ok(mut file) => { - let mut serialized_scene = Vec::new(); - if let Err(why) = file.read_to_end(&mut serialized_scene) { - error!("file read failed: {why:?}"); - } - match Deserializer::from_bytes(&serialized_scene) { - Ok(mut deserializer) => { - // deserializer. - let bla:Fake = ron::from_str("( - resources: {}, - entities: {} - )").unwrap(); - info!("testing {:?}", bla); - info!("YOYO DONE YO !") - } - Err(why) => { - error!("deserializer creation failed: {why:?}"); - } - } - } - Err(why) => { - error!("load failed: {why:?}"); - } - } -} - -fn inject_component_data(world: &mut World, scene: DynamicScene){ - let mut entity_map = EntityMap::default(); - if let Err(why) = scene.write_to_world(world, &mut entity_map) { - panic!("world write failed: {why:?}"); - } - println!("entity map {:?}", entity_map); - // TODO: EntityMap doesn't implement `iter()` - for old_entity in entity_map.keys() { - let entity = entity_map.get(old_entity).unwrap(); - info!("entity update required: {old_entity:?} -> {entity:?}"); - let e_mut = world - .entity_mut(entity); - } - - info!("done loading scene"); -} - -fn post_load(world: &mut World){ - let full_path = "/home/ckaos/projects/grappling-boom-bot/assets/save.ron"; - match File::open(full_path) { - Ok(mut file) => { - let mut serialized_scene = Vec::new(); - if let Err(why) = file.read_to_end(&mut serialized_scene) { - error!("file read failed: {why:?}"); - } - match Deserializer::from_bytes(&serialized_scene) { - Ok(mut deserializer) => { - let result = SceneDeserializer { - type_registry: &world.resource::().read(), - } - .deserialize(&mut deserializer); - info!("deserialize done"); - match result { - Ok(scene) => { - info!("scene loaded"); - // scene.write_to_world(world, entity_map) - // println!("{:?}", scene.entities); - inject_component_data(world, scene); - /*for dyn_ent in scene.entities.iter(){ - // let mut query = scene.world.query::<(Entity, &Name, &GltfExtras, &Parent)>(); - }*/ - } - Err(why) => { - error!("deserialization failed: {why:?}"); - } - } - } - Err(why) => { - error!("deserializer creation failed: {why:?}"); - } - } - } - Err(why) => { - error!("load failed: {why:?}"); - } - } - -} - - - -#[derive(Component, Reflect, Debug, Default )] -#[reflect(Component)] -pub struct Hackish; - - - -/// unload saveables -fn unload_saveables(world: &mut World) { - let entities: Vec = world - .query_filtered::>()// our level/world contains this component - .iter(world) - .collect(); - for entity in entities { - // Check the entity again in case it was despawned recursively - if world.get_entity(entity).is_some() { - info!("despawning"); - world.entity_mut(entity).despawn_recursive(); - } - } -} \ No newline at end of file diff --git a/examples/bevy_gltf_blueprints/multiple_levels/src/core/save_load/saveable.rs b/examples/bevy_gltf_blueprints/multiple_levels/src/core/save_load/saveable.rs deleted file mode 100644 index 67a4c657..00000000 --- a/examples/bevy_gltf_blueprints/multiple_levels/src/core/save_load/saveable.rs +++ /dev/null @@ -1,14 +0,0 @@ -use bevy::prelude::*; -use bevy::utils::Uuid; - -#[derive(Component, Reflect, Debug)] -#[reflect(Component)] -pub struct Saveable { - id: Uuid, -} - -impl Default for Saveable { - fn default() -> Self { - Saveable { id: Uuid::new_v4() } - } -} diff --git a/examples/bevy_gltf_blueprints/multiple_levels/src/core/save_load/saving.rs b/examples/bevy_gltf_blueprints/multiple_levels/src/core/save_load/saving.rs deleted file mode 100644 index 46d0b1af..00000000 --- a/examples/bevy_gltf_blueprints/multiple_levels/src/core/save_load/saving.rs +++ /dev/null @@ -1,87 +0,0 @@ -use bevy::pbr::{Clusters, VisiblePointLights}; -use bevy::render::camera::CameraRenderGraph; -use bevy::render::view::VisibleEntities; -use bevy::tasks::IoTaskPool; -use bevy::{gltf::GltfExtras, prelude::*}; -use bevy_rapier3d::prelude::RigidBody; -use std::fs::File; -use std::io::Write; - -use crate::core::physics::Collider; -use crate::game::{Pickable, Player}; - -use super::Saveable; - -const NEW_SCENE_FILE_PATH: &str = "save.scn.ron"; - -#[derive(Component, Event)] -pub struct SaveRequest { - pub path: String, -} - -pub fn should_save( - // keycode: Res>, - save_requested_events: EventReader, -) -> bool { - return save_requested_events.len() > 0; - - // return keycode.just_pressed(KeyCode::S) -} - -pub fn save_game( - world: &mut World, - // save_requested_events: EventReader, -) { - info!("saving"); - // world. - /*for bli in save_requested_events.iter(){ - println!("SAAAAVE TO THISSSSS {:?}", bli.path) - }*/ - - let saveable_entities: Vec = world - .query_filtered::>() - .iter(world) - .collect(); - - /*let static_entities: Vec = world - .query_filtered::>() - .iter(world) - .collect();*/ - println!("saveable entities {}", saveable_entities.len()); - - let mut scene_builder = DynamicSceneBuilder::from_world(world); - scene_builder - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - // camera stuff - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - //.deny::() - .extract_entities(saveable_entities.into_iter()); - - let dyn_scene = scene_builder.build(); - let serialized_scene = dyn_scene - .serialize_ron(world.resource::()) - .unwrap(); - - #[cfg(not(target_arch = "wasm32"))] - IoTaskPool::get() - .spawn(async move { - // Write the scene RON data to file - File::create(format!("assets/scenes/{NEW_SCENE_FILE_PATH}")) - .and_then(|mut file| file.write(serialized_scene.as_bytes())) - .expect("Error while writing scene to file"); - }) - .detach(); -} diff --git a/examples/bevy_gltf_blueprints/multiple_levels/src/game/in_game.rs b/examples/bevy_gltf_blueprints/multiple_levels/src/game/in_game.rs index add9561c..4047b83e 100644 --- a/examples/bevy_gltf_blueprints/multiple_levels/src/game/in_game.rs +++ b/examples/bevy_gltf_blueprints/multiple_levels/src/game/in_game.rs @@ -67,13 +67,12 @@ pub fn spawn_test( .spawn(( BluePrintBundle { blueprint: BlueprintName("Health_Pickup".to_string()), - transform: TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), ..Default::default() }, bevy::prelude::Name::from(format!("test{}", name_index)), // BlueprintName("Health_Pickup".to_string()), // SpawnHere, - // TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), + TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), Velocity { linvel: Vec3::new(vel_x, vel_y, vel_z), angvel: Vec3::new(0.0, 0.0, 0.0), diff --git a/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/core/mod.rs b/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/core/mod.rs index c253af1c..610f2054 100644 --- a/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/core/mod.rs +++ b/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/core/mod.rs @@ -10,9 +10,6 @@ pub use relationships::*; pub mod physics; pub use physics::*; -// pub mod save_load; -// pub use save_load::*; - use bevy::prelude::*; use bevy_gltf_blueprints::*; @@ -23,7 +20,6 @@ impl Plugin for CorePlugin { LightingPlugin, CameraPlugin, PhysicsPlugin, - // SaveLoadPlugin, BlueprintsPlugin { library_folder: "models/library".into(), material_library: true, diff --git a/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/core/save_load/loading.rs b/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/core/save_load/loading.rs deleted file mode 100644 index 73ef523d..00000000 --- a/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/core/save_load/loading.rs +++ /dev/null @@ -1,218 +0,0 @@ -use bevy::prelude::*; -use bevy_gltf_blueprints::{clone_entity::CloneEntity, GameWorldTag, SpawnHere}; - -use crate::{ - assets::GameAssets, - state::{AppState, GameState, InAppRunning}, -}; - -use super::Saveable; - -const SCENE_FILE_PATH: &str = "scenes/save.scn.ron"; - -#[derive(Component, Debug)] -pub struct TempLoadedSceneMarker; - -#[derive(Component, Debug)] -pub struct SaveablesToRemove(Vec<(Entity, Name)>); - -#[derive(Component, Event)] -pub struct LoadRequest { - pub path: String, -} - -pub fn should_load(save_requested_events: EventReader) -> bool { - return save_requested_events.len() > 0; -} - -pub fn load_prepare( - mut next_app_state: ResMut>, - mut next_game_state: ResMut>, -) { - next_app_state.set(AppState::LoadingGame); - next_game_state.set(GameState::None); - info!("--loading: prepare") -} - -/// unload the level recursively -pub fn _unload_world_old(world: &mut World) { - let entities: Vec = world - // .query_filtered::, With)>>() - .query_filtered::>() // our level/world contains this component - .iter(world) - .collect(); - for entity in entities { - // Check the entity again in case it was despawned recursively - if world.get_entity(entity).is_some() { - world.entity_mut(entity).despawn_recursive(); - } - } -} - -pub fn unload_world(mut commands: Commands, gameworlds: Query>) { - for e in gameworlds.iter() { - info!("--loading: despawn old world/level"); - commands.entity(e).despawn_recursive(); - } -} - -// almost identical to setup_game, !!?? -pub fn load_world( - mut commands: Commands, - game_assets: Res, - // scenes: ResMut, -) { - info!("--loading: loading world/level"); - - commands.spawn(( - SceneBundle { - scene: game_assets.world.clone(), - ..default() - }, - bevy::prelude::Name::from("world"), - GameWorldTag, - InAppRunning, - )); -} - -pub fn load_saved_scene(mut commands: Commands, asset_server: Res) { - commands.spawn(( - DynamicSceneBundle { - // Scenes are loaded just like any other asset. - scene: asset_server.load(SCENE_FILE_PATH), - ..default() - }, - TempLoadedSceneMarker, - )); - // commands.entity(world).add_child(child_scene); - info!("--loading: loaded saved scene"); -} - -pub fn process_loaded_scene( - loaded_scene: Query<(Entity, &Children), With>, - named_entities: Query<(Entity, &Name, &Parent)>, // FIXME: very inneficient - mut commands: Commands, - - mut game_world: Query<(Entity, &Children), With>, - saveables: Query<(Entity, &Name), With>, - asset_server: Res, -) { - for (loaded_scene, children) in loaded_scene.iter() { - info!("--loading: post processing loaded scene"); - - let mut entities_to_load: Vec<(Entity, Name)> = vec![]; - - for loaded_entity in children.iter() { - if let Ok((source, name, _)) = named_entities.get(*loaded_entity) { - entities_to_load.push((source, name.clone())); - - let mut found = false; - for (e, n, p) in named_entities.iter() { - // if we have an entity with the same name as in same file, overwrite - if e != source && name.as_str() == n.as_str() { - // println!("found entity with same name {} {} {:?} {:?}", name, n, source, e); - // source is entity within the newly loaded scene (source), e is within the existing world (destination) - info!("copying data from {:?} to {:?}", source, e); - commands.add(CloneEntity { - source: source, - destination: e, - }); - // FIXME: issue with hierarchy & parenting, would be nice to be able to filter out components from CloneEntity - commands.entity(p.get()).add_child(e); - commands.entity(source).despawn_recursive(); - found = true; - break; - } - } - // entity not found in the list of existing entities (ie entities that came as part of the level) - // so we spawn a new one - if !found { - info!("generating new entity"); - let world = game_world.single_mut(); - let world = world.1[0]; - - let new_entity = commands - .spawn((bevy::prelude::Name::from(name.clone()), SpawnHere)) - .id(); - - commands.add(CloneEntity { - source: source, - destination: new_entity, - }); - - commands.entity(world).add_child(new_entity); - info!("copying data from {:?} to {:?}", source, new_entity); - } - } - } - commands.spawn(SaveablesToRemove(entities_to_load.clone())); - - // if an entity is present in the world but NOT in the saved entities , it should be removed from the world - // ideally this should be run between spawning of the world/level AND spawn_placeholders - - // remove the dynamic scene - info!("--loading: DESPAWNING LOADED SCENE"); - commands.entity(loaded_scene).despawn_recursive(); - - asset_server.mark_unused_assets(); - asset_server.free_unused_assets(); - } - //for saveable in saveables.iter(){ - // println!("SAVEABLE BEFORE {:?}", saveable) - //} -} - -pub fn final_cleanup( - saveables_to_remove: Query<(Entity, &SaveablesToRemove)>, - mut commands: Commands, - saveables: Query<(Entity, &Name), With>, - mut next_app_state: ResMut>, - mut next_game_state: ResMut>, -) { - if let Ok((e, entities_to_load)) = saveables_to_remove.get_single() { - info!("saveables to remove {:?}", entities_to_load); - for (e, n) in saveables.iter() { - let mut found = false; - println!("SAVEABLE {}", n); - - //let entities_to_load = saveables_to_remove.single(); - for (en, na) in entities_to_load.0.iter() { - found = na.as_str() == n.as_str(); - if found { - break; - } - } - if !found { - println!("REMOVING THIS ONE {}", n); - commands.entity(e).despawn_recursive(); - } - } - // if there is a saveable that is NOT in the list of entities to load, despawn it - - // despawn list - commands.entity(e).despawn_recursive(); - - info!("--loading: done, move to InGame state"); - // next_app_state.set(AppState::AppRunning); - next_game_state.set(GameState::InGame); - } -} - -fn process_loaded_scene_load_alt( - entities: Query<(Entity, &Children), With>, - named_entities: Query<(Entity, &Name, &Parent)>, // FIXME: very inneficient - mut commands: Commands, -) { - for (entity, children) in entities.iter() { - let mut entities_to_load: Vec<(Entity, Name)> = vec![]; - for saved_source in children.iter() { - if let Ok((source, name, _)) = named_entities.get(*saved_source) { - println!("AAAAAAA {}", name); - entities_to_load.push((source, name.clone())); - } - } - println!("entities to load {:?}", entities_to_load); - - commands.entity(entity).despawn_recursive(); - } -} diff --git a/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/core/save_load/mod.rs b/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/core/save_load/mod.rs deleted file mode 100644 index 3d0e91ed..00000000 --- a/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/core/save_load/mod.rs +++ /dev/null @@ -1,70 +0,0 @@ -pub mod saveable; -use bevy::asset::free_unused_assets_system; -use bevy_gltf_components::GltfComponentsSet; -pub use saveable::*; - -pub mod saving; -pub use saving::*; - -pub mod loading; -pub use loading::*; - -use bevy::prelude::*; -use bevy::prelude::{App, IntoSystemConfigs, Plugin}; -use bevy::utils::Uuid; - -use bevy_gltf_blueprints::GltfBlueprintsSet; - -#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] -pub enum LoadingSet { - Load, - PostLoad, -} - -pub struct SaveLoadPlugin; -impl Plugin for SaveLoadPlugin { - fn build(&self, app: &mut App) { - app - .register_type::() - .register_type::() - .add_event::() - .add_event::() - - .configure_sets( - Update, - (LoadingSet::Load, LoadingSet::PostLoad) - .chain() - .before(GltfBlueprintsSet::Spawn) - .before(GltfComponentsSet::Injection) - ) - - .add_systems(PreUpdate, save_game.run_if(should_save)) - - .add_systems(Update, - ( - load_prepare, - unload_world, - load_world, - load_saved_scene, - // process_loaded_scene - ) - .chain() - .run_if(should_load) // .run_if(in_state(AppState::AppRunning)) - .in_set(LoadingSet::Load) - ) - .add_systems(Update, - ( - process_loaded_scene, - apply_deferred, - final_cleanup, - apply_deferred, - free_unused_assets_system - ) - .chain() - .in_set(LoadingSet::PostLoad) - ) - - // .add_systems(Update, bla) - ; - } -} diff --git a/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/core/save_load/old.rs b/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/core/save_load/old.rs deleted file mode 100644 index 7d8a3899..00000000 --- a/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/core/save_load/old.rs +++ /dev/null @@ -1,137 +0,0 @@ -const NEW_SCENE_FILE_PATH:&str="save.scn.ron"; - - - - -use bevy::ecs::component::Components; -use bevy::ecs::entity::EntityMap; -use serde::{Deserialize, Serialize}; - - -use std::io::Read; -use bevy::scene::serde::SceneDeserializer; -use ron::Deserializer; -use serde::de::DeserializeSeed; - - - - -#[derive(Debug, Deserialize)] -struct Components2; - -#[derive(Debug, Deserialize)] -struct Fake { - resources: HashMap, - entities: HashMap -} - -fn ron_test(){ - let full_path = "/home/ckaos/projects/grappling-boom-bot/assets/save.ron"; - match File::open(full_path) { - Ok(mut file) => { - let mut serialized_scene = Vec::new(); - if let Err(why) = file.read_to_end(&mut serialized_scene) { - error!("file read failed: {why:?}"); - } - match Deserializer::from_bytes(&serialized_scene) { - Ok(mut deserializer) => { - // deserializer. - let bla:Fake = ron::from_str("( - resources: {}, - entities: {} - )").unwrap(); - info!("testing {:?}", bla); - info!("YOYO DONE YO !") - } - Err(why) => { - error!("deserializer creation failed: {why:?}"); - } - } - } - Err(why) => { - error!("load failed: {why:?}"); - } - } -} - -fn inject_component_data(world: &mut World, scene: DynamicScene){ - let mut entity_map = EntityMap::default(); - if let Err(why) = scene.write_to_world(world, &mut entity_map) { - panic!("world write failed: {why:?}"); - } - println!("entity map {:?}", entity_map); - // TODO: EntityMap doesn't implement `iter()` - for old_entity in entity_map.keys() { - let entity = entity_map.get(old_entity).unwrap(); - info!("entity update required: {old_entity:?} -> {entity:?}"); - let e_mut = world - .entity_mut(entity); - } - - info!("done loading scene"); -} - -fn post_load(world: &mut World){ - let full_path = "/home/ckaos/projects/grappling-boom-bot/assets/save.ron"; - match File::open(full_path) { - Ok(mut file) => { - let mut serialized_scene = Vec::new(); - if let Err(why) = file.read_to_end(&mut serialized_scene) { - error!("file read failed: {why:?}"); - } - match Deserializer::from_bytes(&serialized_scene) { - Ok(mut deserializer) => { - let result = SceneDeserializer { - type_registry: &world.resource::().read(), - } - .deserialize(&mut deserializer); - info!("deserialize done"); - match result { - Ok(scene) => { - info!("scene loaded"); - // scene.write_to_world(world, entity_map) - // println!("{:?}", scene.entities); - inject_component_data(world, scene); - /*for dyn_ent in scene.entities.iter(){ - // let mut query = scene.world.query::<(Entity, &Name, &GltfExtras, &Parent)>(); - }*/ - } - Err(why) => { - error!("deserialization failed: {why:?}"); - } - } - } - Err(why) => { - error!("deserializer creation failed: {why:?}"); - } - } - } - Err(why) => { - error!("load failed: {why:?}"); - } - } - -} - - - -#[derive(Component, Reflect, Debug, Default )] -#[reflect(Component)] -pub struct Hackish; - - - -/// unload saveables -fn unload_saveables(world: &mut World) { - let entities: Vec = world - .query_filtered::>()// our level/world contains this component - .iter(world) - .collect(); - for entity in entities { - // Check the entity again in case it was despawned recursively - if world.get_entity(entity).is_some() { - info!("despawning"); - world.entity_mut(entity).despawn_recursive(); - } - } -} \ No newline at end of file diff --git a/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/core/save_load/saveable.rs b/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/core/save_load/saveable.rs deleted file mode 100644 index 67a4c657..00000000 --- a/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/core/save_load/saveable.rs +++ /dev/null @@ -1,14 +0,0 @@ -use bevy::prelude::*; -use bevy::utils::Uuid; - -#[derive(Component, Reflect, Debug)] -#[reflect(Component)] -pub struct Saveable { - id: Uuid, -} - -impl Default for Saveable { - fn default() -> Self { - Saveable { id: Uuid::new_v4() } - } -} diff --git a/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/core/save_load/saving.rs b/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/core/save_load/saving.rs deleted file mode 100644 index 46d0b1af..00000000 --- a/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/core/save_load/saving.rs +++ /dev/null @@ -1,87 +0,0 @@ -use bevy::pbr::{Clusters, VisiblePointLights}; -use bevy::render::camera::CameraRenderGraph; -use bevy::render::view::VisibleEntities; -use bevy::tasks::IoTaskPool; -use bevy::{gltf::GltfExtras, prelude::*}; -use bevy_rapier3d::prelude::RigidBody; -use std::fs::File; -use std::io::Write; - -use crate::core::physics::Collider; -use crate::game::{Pickable, Player}; - -use super::Saveable; - -const NEW_SCENE_FILE_PATH: &str = "save.scn.ron"; - -#[derive(Component, Event)] -pub struct SaveRequest { - pub path: String, -} - -pub fn should_save( - // keycode: Res>, - save_requested_events: EventReader, -) -> bool { - return save_requested_events.len() > 0; - - // return keycode.just_pressed(KeyCode::S) -} - -pub fn save_game( - world: &mut World, - // save_requested_events: EventReader, -) { - info!("saving"); - // world. - /*for bli in save_requested_events.iter(){ - println!("SAAAAVE TO THISSSSS {:?}", bli.path) - }*/ - - let saveable_entities: Vec = world - .query_filtered::>() - .iter(world) - .collect(); - - /*let static_entities: Vec = world - .query_filtered::>() - .iter(world) - .collect();*/ - println!("saveable entities {}", saveable_entities.len()); - - let mut scene_builder = DynamicSceneBuilder::from_world(world); - scene_builder - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - // camera stuff - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - //.deny::() - .extract_entities(saveable_entities.into_iter()); - - let dyn_scene = scene_builder.build(); - let serialized_scene = dyn_scene - .serialize_ron(world.resource::()) - .unwrap(); - - #[cfg(not(target_arch = "wasm32"))] - IoTaskPool::get() - .spawn(async move { - // Write the scene RON data to file - File::create(format!("assets/scenes/{NEW_SCENE_FILE_PATH}")) - .and_then(|mut file| file.write(serialized_scene.as_bytes())) - .expect("Error while writing scene to file"); - }) - .detach(); -} diff --git a/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/game/in_game.rs b/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/game/in_game.rs index add9561c..4047b83e 100644 --- a/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/game/in_game.rs +++ b/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/game/in_game.rs @@ -67,13 +67,12 @@ pub fn spawn_test( .spawn(( BluePrintBundle { blueprint: BlueprintName("Health_Pickup".to_string()), - transform: TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), ..Default::default() }, bevy::prelude::Name::from(format!("test{}", name_index)), // BlueprintName("Health_Pickup".to_string()), // SpawnHere, - // TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), + TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), Velocity { linvel: Vec3::new(vel_x, vel_y, vel_z), angvel: Vec3::new(0.0, 0.0, 0.0), diff --git a/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/game/level_transitions.rs b/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/game/level_transitions.rs index 2cfc8e0f..b59494aa 100644 --- a/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/game/level_transitions.rs +++ b/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/game/level_transitions.rs @@ -30,7 +30,6 @@ pub fn trigger_level_transition( // we need to accomodate for the fact that the collider may be a child of the level transition (FIXME: is this a missunderstanding on my part about rapier child colliders ?) let entity1_parent = parents.get(*entity1).unwrap(); let entity2_parent = parents.get(*entity2).unwrap(); - if level_transition_triggers.get(*entity1).is_ok() || level_transition_triggers.get(*entity2).is_ok() @@ -51,10 +50,13 @@ pub fn trigger_level_transition( level_transition_triggers.get(entity2_parent.get()).unwrap(); } - if players.get(*entity1).is_ok() || players.get(entity1_parent.get()).is_ok() || players.get(*entity2).is_ok() || players.get(entity2_parent.get()).is_ok() { + if players.get(*entity1).is_ok() + || players.get(entity1_parent.get()).is_ok() + || players.get(*entity2).is_ok() + || players.get(entity2_parent.get()).is_ok() + { println!("one entity is the player, we can enter") - } - else { + } else { // if none of our entities is a player, bail out, as only entities with player components should trigger a transition return; } diff --git a/examples/bevy_gltf_blueprints/nested_blueprints/src/core/mod.rs b/examples/bevy_gltf_blueprints/nested_blueprints/src/core/mod.rs index f9145633..9c38fa77 100644 --- a/examples/bevy_gltf_blueprints/nested_blueprints/src/core/mod.rs +++ b/examples/bevy_gltf_blueprints/nested_blueprints/src/core/mod.rs @@ -10,9 +10,6 @@ pub use relationships::*; pub mod physics; pub use physics::*; -// pub mod save_load; -// pub use save_load::*; - use bevy::prelude::*; use bevy_gltf_blueprints::*; @@ -23,7 +20,6 @@ impl Plugin for CorePlugin { LightingPlugin, CameraPlugin, PhysicsPlugin, - // SaveLoadPlugin, BlueprintsPlugin { library_folder: "models/library".into(), format: GltfFormat::GLB, diff --git a/examples/bevy_gltf_blueprints/nested_blueprints/src/core/save_load/loading.rs b/examples/bevy_gltf_blueprints/nested_blueprints/src/core/save_load/loading.rs deleted file mode 100644 index 73ef523d..00000000 --- a/examples/bevy_gltf_blueprints/nested_blueprints/src/core/save_load/loading.rs +++ /dev/null @@ -1,218 +0,0 @@ -use bevy::prelude::*; -use bevy_gltf_blueprints::{clone_entity::CloneEntity, GameWorldTag, SpawnHere}; - -use crate::{ - assets::GameAssets, - state::{AppState, GameState, InAppRunning}, -}; - -use super::Saveable; - -const SCENE_FILE_PATH: &str = "scenes/save.scn.ron"; - -#[derive(Component, Debug)] -pub struct TempLoadedSceneMarker; - -#[derive(Component, Debug)] -pub struct SaveablesToRemove(Vec<(Entity, Name)>); - -#[derive(Component, Event)] -pub struct LoadRequest { - pub path: String, -} - -pub fn should_load(save_requested_events: EventReader) -> bool { - return save_requested_events.len() > 0; -} - -pub fn load_prepare( - mut next_app_state: ResMut>, - mut next_game_state: ResMut>, -) { - next_app_state.set(AppState::LoadingGame); - next_game_state.set(GameState::None); - info!("--loading: prepare") -} - -/// unload the level recursively -pub fn _unload_world_old(world: &mut World) { - let entities: Vec = world - // .query_filtered::, With)>>() - .query_filtered::>() // our level/world contains this component - .iter(world) - .collect(); - for entity in entities { - // Check the entity again in case it was despawned recursively - if world.get_entity(entity).is_some() { - world.entity_mut(entity).despawn_recursive(); - } - } -} - -pub fn unload_world(mut commands: Commands, gameworlds: Query>) { - for e in gameworlds.iter() { - info!("--loading: despawn old world/level"); - commands.entity(e).despawn_recursive(); - } -} - -// almost identical to setup_game, !!?? -pub fn load_world( - mut commands: Commands, - game_assets: Res, - // scenes: ResMut, -) { - info!("--loading: loading world/level"); - - commands.spawn(( - SceneBundle { - scene: game_assets.world.clone(), - ..default() - }, - bevy::prelude::Name::from("world"), - GameWorldTag, - InAppRunning, - )); -} - -pub fn load_saved_scene(mut commands: Commands, asset_server: Res) { - commands.spawn(( - DynamicSceneBundle { - // Scenes are loaded just like any other asset. - scene: asset_server.load(SCENE_FILE_PATH), - ..default() - }, - TempLoadedSceneMarker, - )); - // commands.entity(world).add_child(child_scene); - info!("--loading: loaded saved scene"); -} - -pub fn process_loaded_scene( - loaded_scene: Query<(Entity, &Children), With>, - named_entities: Query<(Entity, &Name, &Parent)>, // FIXME: very inneficient - mut commands: Commands, - - mut game_world: Query<(Entity, &Children), With>, - saveables: Query<(Entity, &Name), With>, - asset_server: Res, -) { - for (loaded_scene, children) in loaded_scene.iter() { - info!("--loading: post processing loaded scene"); - - let mut entities_to_load: Vec<(Entity, Name)> = vec![]; - - for loaded_entity in children.iter() { - if let Ok((source, name, _)) = named_entities.get(*loaded_entity) { - entities_to_load.push((source, name.clone())); - - let mut found = false; - for (e, n, p) in named_entities.iter() { - // if we have an entity with the same name as in same file, overwrite - if e != source && name.as_str() == n.as_str() { - // println!("found entity with same name {} {} {:?} {:?}", name, n, source, e); - // source is entity within the newly loaded scene (source), e is within the existing world (destination) - info!("copying data from {:?} to {:?}", source, e); - commands.add(CloneEntity { - source: source, - destination: e, - }); - // FIXME: issue with hierarchy & parenting, would be nice to be able to filter out components from CloneEntity - commands.entity(p.get()).add_child(e); - commands.entity(source).despawn_recursive(); - found = true; - break; - } - } - // entity not found in the list of existing entities (ie entities that came as part of the level) - // so we spawn a new one - if !found { - info!("generating new entity"); - let world = game_world.single_mut(); - let world = world.1[0]; - - let new_entity = commands - .spawn((bevy::prelude::Name::from(name.clone()), SpawnHere)) - .id(); - - commands.add(CloneEntity { - source: source, - destination: new_entity, - }); - - commands.entity(world).add_child(new_entity); - info!("copying data from {:?} to {:?}", source, new_entity); - } - } - } - commands.spawn(SaveablesToRemove(entities_to_load.clone())); - - // if an entity is present in the world but NOT in the saved entities , it should be removed from the world - // ideally this should be run between spawning of the world/level AND spawn_placeholders - - // remove the dynamic scene - info!("--loading: DESPAWNING LOADED SCENE"); - commands.entity(loaded_scene).despawn_recursive(); - - asset_server.mark_unused_assets(); - asset_server.free_unused_assets(); - } - //for saveable in saveables.iter(){ - // println!("SAVEABLE BEFORE {:?}", saveable) - //} -} - -pub fn final_cleanup( - saveables_to_remove: Query<(Entity, &SaveablesToRemove)>, - mut commands: Commands, - saveables: Query<(Entity, &Name), With>, - mut next_app_state: ResMut>, - mut next_game_state: ResMut>, -) { - if let Ok((e, entities_to_load)) = saveables_to_remove.get_single() { - info!("saveables to remove {:?}", entities_to_load); - for (e, n) in saveables.iter() { - let mut found = false; - println!("SAVEABLE {}", n); - - //let entities_to_load = saveables_to_remove.single(); - for (en, na) in entities_to_load.0.iter() { - found = na.as_str() == n.as_str(); - if found { - break; - } - } - if !found { - println!("REMOVING THIS ONE {}", n); - commands.entity(e).despawn_recursive(); - } - } - // if there is a saveable that is NOT in the list of entities to load, despawn it - - // despawn list - commands.entity(e).despawn_recursive(); - - info!("--loading: done, move to InGame state"); - // next_app_state.set(AppState::AppRunning); - next_game_state.set(GameState::InGame); - } -} - -fn process_loaded_scene_load_alt( - entities: Query<(Entity, &Children), With>, - named_entities: Query<(Entity, &Name, &Parent)>, // FIXME: very inneficient - mut commands: Commands, -) { - for (entity, children) in entities.iter() { - let mut entities_to_load: Vec<(Entity, Name)> = vec![]; - for saved_source in children.iter() { - if let Ok((source, name, _)) = named_entities.get(*saved_source) { - println!("AAAAAAA {}", name); - entities_to_load.push((source, name.clone())); - } - } - println!("entities to load {:?}", entities_to_load); - - commands.entity(entity).despawn_recursive(); - } -} diff --git a/examples/bevy_gltf_blueprints/nested_blueprints/src/core/save_load/mod.rs b/examples/bevy_gltf_blueprints/nested_blueprints/src/core/save_load/mod.rs deleted file mode 100644 index 3d0e91ed..00000000 --- a/examples/bevy_gltf_blueprints/nested_blueprints/src/core/save_load/mod.rs +++ /dev/null @@ -1,70 +0,0 @@ -pub mod saveable; -use bevy::asset::free_unused_assets_system; -use bevy_gltf_components::GltfComponentsSet; -pub use saveable::*; - -pub mod saving; -pub use saving::*; - -pub mod loading; -pub use loading::*; - -use bevy::prelude::*; -use bevy::prelude::{App, IntoSystemConfigs, Plugin}; -use bevy::utils::Uuid; - -use bevy_gltf_blueprints::GltfBlueprintsSet; - -#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] -pub enum LoadingSet { - Load, - PostLoad, -} - -pub struct SaveLoadPlugin; -impl Plugin for SaveLoadPlugin { - fn build(&self, app: &mut App) { - app - .register_type::() - .register_type::() - .add_event::() - .add_event::() - - .configure_sets( - Update, - (LoadingSet::Load, LoadingSet::PostLoad) - .chain() - .before(GltfBlueprintsSet::Spawn) - .before(GltfComponentsSet::Injection) - ) - - .add_systems(PreUpdate, save_game.run_if(should_save)) - - .add_systems(Update, - ( - load_prepare, - unload_world, - load_world, - load_saved_scene, - // process_loaded_scene - ) - .chain() - .run_if(should_load) // .run_if(in_state(AppState::AppRunning)) - .in_set(LoadingSet::Load) - ) - .add_systems(Update, - ( - process_loaded_scene, - apply_deferred, - final_cleanup, - apply_deferred, - free_unused_assets_system - ) - .chain() - .in_set(LoadingSet::PostLoad) - ) - - // .add_systems(Update, bla) - ; - } -} diff --git a/examples/bevy_gltf_blueprints/nested_blueprints/src/core/save_load/old.rs b/examples/bevy_gltf_blueprints/nested_blueprints/src/core/save_load/old.rs deleted file mode 100644 index 7d8a3899..00000000 --- a/examples/bevy_gltf_blueprints/nested_blueprints/src/core/save_load/old.rs +++ /dev/null @@ -1,137 +0,0 @@ -const NEW_SCENE_FILE_PATH:&str="save.scn.ron"; - - - - -use bevy::ecs::component::Components; -use bevy::ecs::entity::EntityMap; -use serde::{Deserialize, Serialize}; - - -use std::io::Read; -use bevy::scene::serde::SceneDeserializer; -use ron::Deserializer; -use serde::de::DeserializeSeed; - - - - -#[derive(Debug, Deserialize)] -struct Components2; - -#[derive(Debug, Deserialize)] -struct Fake { - resources: HashMap, - entities: HashMap -} - -fn ron_test(){ - let full_path = "/home/ckaos/projects/grappling-boom-bot/assets/save.ron"; - match File::open(full_path) { - Ok(mut file) => { - let mut serialized_scene = Vec::new(); - if let Err(why) = file.read_to_end(&mut serialized_scene) { - error!("file read failed: {why:?}"); - } - match Deserializer::from_bytes(&serialized_scene) { - Ok(mut deserializer) => { - // deserializer. - let bla:Fake = ron::from_str("( - resources: {}, - entities: {} - )").unwrap(); - info!("testing {:?}", bla); - info!("YOYO DONE YO !") - } - Err(why) => { - error!("deserializer creation failed: {why:?}"); - } - } - } - Err(why) => { - error!("load failed: {why:?}"); - } - } -} - -fn inject_component_data(world: &mut World, scene: DynamicScene){ - let mut entity_map = EntityMap::default(); - if let Err(why) = scene.write_to_world(world, &mut entity_map) { - panic!("world write failed: {why:?}"); - } - println!("entity map {:?}", entity_map); - // TODO: EntityMap doesn't implement `iter()` - for old_entity in entity_map.keys() { - let entity = entity_map.get(old_entity).unwrap(); - info!("entity update required: {old_entity:?} -> {entity:?}"); - let e_mut = world - .entity_mut(entity); - } - - info!("done loading scene"); -} - -fn post_load(world: &mut World){ - let full_path = "/home/ckaos/projects/grappling-boom-bot/assets/save.ron"; - match File::open(full_path) { - Ok(mut file) => { - let mut serialized_scene = Vec::new(); - if let Err(why) = file.read_to_end(&mut serialized_scene) { - error!("file read failed: {why:?}"); - } - match Deserializer::from_bytes(&serialized_scene) { - Ok(mut deserializer) => { - let result = SceneDeserializer { - type_registry: &world.resource::().read(), - } - .deserialize(&mut deserializer); - info!("deserialize done"); - match result { - Ok(scene) => { - info!("scene loaded"); - // scene.write_to_world(world, entity_map) - // println!("{:?}", scene.entities); - inject_component_data(world, scene); - /*for dyn_ent in scene.entities.iter(){ - // let mut query = scene.world.query::<(Entity, &Name, &GltfExtras, &Parent)>(); - }*/ - } - Err(why) => { - error!("deserialization failed: {why:?}"); - } - } - } - Err(why) => { - error!("deserializer creation failed: {why:?}"); - } - } - } - Err(why) => { - error!("load failed: {why:?}"); - } - } - -} - - - -#[derive(Component, Reflect, Debug, Default )] -#[reflect(Component)] -pub struct Hackish; - - - -/// unload saveables -fn unload_saveables(world: &mut World) { - let entities: Vec = world - .query_filtered::>()// our level/world contains this component - .iter(world) - .collect(); - for entity in entities { - // Check the entity again in case it was despawned recursively - if world.get_entity(entity).is_some() { - info!("despawning"); - world.entity_mut(entity).despawn_recursive(); - } - } -} \ No newline at end of file diff --git a/examples/bevy_gltf_blueprints/nested_blueprints/src/core/save_load/saveable.rs b/examples/bevy_gltf_blueprints/nested_blueprints/src/core/save_load/saveable.rs deleted file mode 100644 index 67a4c657..00000000 --- a/examples/bevy_gltf_blueprints/nested_blueprints/src/core/save_load/saveable.rs +++ /dev/null @@ -1,14 +0,0 @@ -use bevy::prelude::*; -use bevy::utils::Uuid; - -#[derive(Component, Reflect, Debug)] -#[reflect(Component)] -pub struct Saveable { - id: Uuid, -} - -impl Default for Saveable { - fn default() -> Self { - Saveable { id: Uuid::new_v4() } - } -} diff --git a/examples/bevy_gltf_blueprints/nested_blueprints/src/core/save_load/saving.rs b/examples/bevy_gltf_blueprints/nested_blueprints/src/core/save_load/saving.rs deleted file mode 100644 index 46d0b1af..00000000 --- a/examples/bevy_gltf_blueprints/nested_blueprints/src/core/save_load/saving.rs +++ /dev/null @@ -1,87 +0,0 @@ -use bevy::pbr::{Clusters, VisiblePointLights}; -use bevy::render::camera::CameraRenderGraph; -use bevy::render::view::VisibleEntities; -use bevy::tasks::IoTaskPool; -use bevy::{gltf::GltfExtras, prelude::*}; -use bevy_rapier3d::prelude::RigidBody; -use std::fs::File; -use std::io::Write; - -use crate::core::physics::Collider; -use crate::game::{Pickable, Player}; - -use super::Saveable; - -const NEW_SCENE_FILE_PATH: &str = "save.scn.ron"; - -#[derive(Component, Event)] -pub struct SaveRequest { - pub path: String, -} - -pub fn should_save( - // keycode: Res>, - save_requested_events: EventReader, -) -> bool { - return save_requested_events.len() > 0; - - // return keycode.just_pressed(KeyCode::S) -} - -pub fn save_game( - world: &mut World, - // save_requested_events: EventReader, -) { - info!("saving"); - // world. - /*for bli in save_requested_events.iter(){ - println!("SAAAAVE TO THISSSSS {:?}", bli.path) - }*/ - - let saveable_entities: Vec = world - .query_filtered::>() - .iter(world) - .collect(); - - /*let static_entities: Vec = world - .query_filtered::>() - .iter(world) - .collect();*/ - println!("saveable entities {}", saveable_entities.len()); - - let mut scene_builder = DynamicSceneBuilder::from_world(world); - scene_builder - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - // camera stuff - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - .deny::() - //.deny::() - .extract_entities(saveable_entities.into_iter()); - - let dyn_scene = scene_builder.build(); - let serialized_scene = dyn_scene - .serialize_ron(world.resource::()) - .unwrap(); - - #[cfg(not(target_arch = "wasm32"))] - IoTaskPool::get() - .spawn(async move { - // Write the scene RON data to file - File::create(format!("assets/scenes/{NEW_SCENE_FILE_PATH}")) - .and_then(|mut file| file.write(serialized_scene.as_bytes())) - .expect("Error while writing scene to file"); - }) - .detach(); -} diff --git a/examples/bevy_gltf_blueprints/nested_blueprints/src/game/in_game.rs b/examples/bevy_gltf_blueprints/nested_blueprints/src/game/in_game.rs index 2500bbb3..2047798b 100644 --- a/examples/bevy_gltf_blueprints/nested_blueprints/src/game/in_game.rs +++ b/examples/bevy_gltf_blueprints/nested_blueprints/src/game/in_game.rs @@ -67,13 +67,12 @@ pub fn spawn_test( .spawn(( BluePrintBundle { blueprint: BlueprintName("Health_Pickup".to_string()), - transform: TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), ..Default::default() }, bevy::prelude::Name::from(format!("test{}", name_index)), // BlueprintName("Health_Pickup".to_string()), // SpawnHere, - // TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), + TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), Velocity { linvel: Vec3::new(vel_x, vel_y, vel_z), angvel: Vec3::new(0.0, 0.0, 0.0), diff --git a/examples/bevy_gltf_save_load/basic/Cargo.toml b/examples/bevy_gltf_save_load/basic/Cargo.toml new file mode 100644 index 00000000..71138dfa --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "bevy_gltf_save_load_basic_example" +version = "0.3.0" +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] +bevy="0.12" +bevy_gltf_blueprints = "0.6" +bevy_gltf_save_load = { path = "../../../crates/bevy_gltf_save_load" } + +bevy_rapier3d = { version = "0.23.0", features = [ "serde-serialize", "debug-render-3d", "enhanced-determinism"] } +bevy_asset_loader = { version = "0.18", features = ["standard_dynamic_assets" ]} +bevy_editor_pls = { version = "0.6" } +rand = "0.8.5" +serde_json="1.0.108" +serde="1.0.193" \ No newline at end of file diff --git a/examples/bevy_gltf_save_load/basic/README.md b/examples/bevy_gltf_save_load/basic/README.md new file mode 100644 index 00000000..d680ade1 --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/README.md @@ -0,0 +1,16 @@ +# Basic save_load example/demo + +This example showcases how to use ```bevy_save_load``` crate to save & load your bevy game + +## Notes Workflow with blender / demo information + +- the gltf files for this demo where generated using the **Export dynamic and static objects seperatly** feature of the auto_export addon: +so the static & dynamic level files where generated automatically, based on the entities that have a **Dynamic** component/custom property + + +## Running this example + +``` +cargo run --features bevy/dynamic_linking +``` + diff --git a/examples/bevy_gltf_save_load/basic/assets/assets_core.assets.ron b/examples/bevy_gltf_save_load/basic/assets/assets_core.assets.ron new file mode 100644 index 00000000..8d0a0994 --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/assets/assets_core.assets.ron @@ -0,0 +1 @@ +({}) \ No newline at end of file diff --git a/examples/bevy_gltf_save_load/basic/assets/assets_game.assets.ron b/examples/bevy_gltf_save_load/basic/assets/assets_game.assets.ron new file mode 100644 index 00000000..aa737c48 --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/assets/assets_game.assets.ron @@ -0,0 +1,8 @@ +({ + "world":File (path: "models/World.glb"), + "world_dynamic":File (path: "models/World_dynamic.glb"), + + "models": Folder ( + path: "models/library", + ), +}) \ No newline at end of file diff --git a/examples/bevy_gltf_save_load/basic/assets/basic.blend b/examples/bevy_gltf_save_load/basic/assets/basic.blend new file mode 100644 index 00000000..3538a2a9 Binary files /dev/null and b/examples/bevy_gltf_save_load/basic/assets/basic.blend differ diff --git a/examples/bevy_gltf_save_load/basic/assets/models/World.glb b/examples/bevy_gltf_save_load/basic/assets/models/World.glb new file mode 100644 index 00000000..6366b824 Binary files /dev/null and b/examples/bevy_gltf_save_load/basic/assets/models/World.glb differ diff --git a/examples/bevy_gltf_save_load/basic/assets/models/World_dynamic.glb b/examples/bevy_gltf_save_load/basic/assets/models/World_dynamic.glb new file mode 100644 index 00000000..fc51c4ed Binary files /dev/null and b/examples/bevy_gltf_save_load/basic/assets/models/World_dynamic.glb differ diff --git a/examples/bevy_gltf_save_load/basic/assets/models/library/Container.glb b/examples/bevy_gltf_save_load/basic/assets/models/library/Container.glb new file mode 100644 index 00000000..3fbdb91c Binary files /dev/null and b/examples/bevy_gltf_save_load/basic/assets/models/library/Container.glb differ diff --git a/examples/bevy_gltf_save_load/basic/assets/models/library/Ground.glb b/examples/bevy_gltf_save_load/basic/assets/models/library/Ground.glb new file mode 100644 index 00000000..5f6247ac Binary files /dev/null and b/examples/bevy_gltf_save_load/basic/assets/models/library/Ground.glb differ diff --git a/examples/bevy_gltf_save_load/basic/assets/models/library/Health_Pickup.glb b/examples/bevy_gltf_save_load/basic/assets/models/library/Health_Pickup.glb new file mode 100644 index 00000000..01e90810 Binary files /dev/null and b/examples/bevy_gltf_save_load/basic/assets/models/library/Health_Pickup.glb differ diff --git a/examples/bevy_gltf_save_load/basic/assets/models/library/MagicTeapot.glb b/examples/bevy_gltf_save_load/basic/assets/models/library/MagicTeapot.glb new file mode 100644 index 00000000..d572b13b Binary files /dev/null and b/examples/bevy_gltf_save_load/basic/assets/models/library/MagicTeapot.glb differ diff --git a/examples/bevy_gltf_save_load/basic/assets/models/library/Pillar.glb b/examples/bevy_gltf_save_load/basic/assets/models/library/Pillar.glb new file mode 100644 index 00000000..6ce36be5 Binary files /dev/null and b/examples/bevy_gltf_save_load/basic/assets/models/library/Pillar.glb differ diff --git a/examples/bevy_gltf_save_load/basic/assets/models/library/Player.glb b/examples/bevy_gltf_save_load/basic/assets/models/library/Player.glb new file mode 100644 index 00000000..b9f31498 Binary files /dev/null and b/examples/bevy_gltf_save_load/basic/assets/models/library/Player.glb differ diff --git a/examples/bevy_gltf_save_load/basic/assets/models/library/Sphero.glb b/examples/bevy_gltf_save_load/basic/assets/models/library/Sphero.glb new file mode 100644 index 00000000..9477a17b Binary files /dev/null and b/examples/bevy_gltf_save_load/basic/assets/models/library/Sphero.glb differ diff --git a/examples/bevy_gltf_save_load/basic/assets/models/library/Unused_in_level_test.glb b/examples/bevy_gltf_save_load/basic/assets/models/library/Unused_in_level_test.glb new file mode 100644 index 00000000..001340fe Binary files /dev/null and b/examples/bevy_gltf_save_load/basic/assets/models/library/Unused_in_level_test.glb differ diff --git a/examples/bevy_gltf_save_load/basic/assets/scenes/save.scn.ron b/examples/bevy_gltf_save_load/basic/assets/scenes/save.scn.ron new file mode 100644 index 00000000..c6b2b537 --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/assets/scenes/save.scn.ron @@ -0,0 +1,2536 @@ +( + resources: { + "bevy_gltf_save_load::saving::StaticEntitiesStorage": ( + name: "World", + library_path: "models", + ), + }, + entities: { + 4294967309: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -3.4747214, + y: 2.0, + z: -0.47800493, + ), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 1.0, + y: 0.0, + z: 0.0, + ), + y_axis: ( + x: 0.0, + y: 1.0, + z: 0.0, + ), + z_axis: ( + x: 0.0, + y: 0.0, + z: 1.0, + ), + ), + translation: ( + x: -26.735256, + y: 3.6987834, + z: -24.778835, + ), + )), + "bevy_core::name::Name": ( + hash: 11525084598183387914, + name: "ParentingTest", + ), + "bevy_hierarchy::components::parent::Parent": (4294967355), + "bevy_hierarchy::components::children::Children": ([ + 4294967318, + ]), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Container"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 4294967313: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -2.2050228, + y: 2.0, + z: 5.3109236, + ), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 1.0, + y: 0.0, + z: 0.0, + ), + y_axis: ( + x: 0.0, + y: 1.0, + z: 0.0, + ), + z_axis: ( + x: 0.0, + y: 0.0, + z: 1.0, + ), + ), + translation: ( + x: -27.67058, + y: 5.6987834, + z: -13.678983, + ), + )), + "bevy_core::name::Name": ( + hash: 12227652970378347066, + name: "SubParentingTest", + ), + "bevy_hierarchy::components::parent::Parent": (17179869209), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Sphero"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 4294967314: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -4.2068176, + y: 2.0, + z: 3.3114986, + ), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 1.0, + y: 0.0, + z: 0.0, + ), + y_axis: ( + x: 0.0, + y: 1.0, + z: 0.0, + ), + z_axis: ( + x: 0.0, + y: 0.0, + z: 1.0, + ), + ), + translation: ( + x: -31.67417, + y: 5.6987834, + z: -17.677834, + ), + )), + "bevy_core::name::Name": ( + hash: 12227652970378347066, + name: "SubParentingTest", + ), + "bevy_hierarchy::components::parent::Parent": (4294967329), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Sphero"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 4294967315: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -1.0132551, + y: 2.0, + z: -2.93677, + ), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 1.0, + y: 0.0, + z: 0.0, + ), + y_axis: ( + x: 0.0, + y: 1.0, + z: 0.0, + ), + z_axis: ( + x: 0.0, + y: 0.0, + z: 1.0, + ), + ), + translation: ( + x: -24.273788, + y: 3.6987834, + z: -27.2376, + ), + )), + "bevy_core::name::Name": ( + hash: 11525084598183387914, + name: "ParentingTest", + ), + "bevy_hierarchy::components::parent::Parent": (4294967355), + "bevy_hierarchy::components::children::Children": ([ + 4294967328, + ]), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Container"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 4294967316: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -5.375821, + y: 2.0, + z: -1.2236366, + ), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 1.0, + y: 0.0, + z: 0.0, + ), + y_axis: ( + x: 0.0, + y: 1.0, + z: 0.0, + ), + z_axis: ( + x: 0.0, + y: 0.0, + z: 1.0, + ), + ), + translation: ( + x: -34.012177, + y: 5.6987834, + z: -26.748104, + ), + )), + "bevy_core::name::Name": ( + hash: 12227652970378347066, + name: "SubParentingTest", + ), + "bevy_hierarchy::components::parent::Parent": (12884901893), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Sphero"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 4294967318: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -3.4747214, + y: 2.0, + z: -0.47800493, + ), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 1.0, + y: 0.0, + z: 0.0, + ), + y_axis: ( + x: 0.0, + y: 1.0, + z: 0.0, + ), + z_axis: ( + x: 0.0, + y: 0.0, + z: 1.0, + ), + ), + translation: ( + x: -30.209978, + y: 5.6987834, + z: -25.25684, + ), + )), + "bevy_core::name::Name": ( + hash: 12227652970378347066, + name: "SubParentingTest", + ), + "bevy_hierarchy::components::parent::Parent": (4294967309), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Sphero"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 4294967319: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 1.4628916, + y: 2.0, + z: -5.380514, + ), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 1.0, + y: 0.0, + z: 0.0, + ), + y_axis: ( + x: 0.0, + y: 1.0, + z: 0.0, + ), + z_axis: ( + x: 0.0, + y: 0.0, + z: 1.0, + ), + ), + translation: ( + x: -21.797642, + y: 3.6987834, + z: -29.681345, + ), + )), + "bevy_core::name::Name": ( + hash: 11525084598183387914, + name: "ParentingTest", + ), + "bevy_hierarchy::components::parent::Parent": (4294967355), + "bevy_hierarchy::components::children::Children": ([ + 4294967323, + ]), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Container"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 4294967322: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -5.3368125, + y: 2.0, + z: -4.6879, + ), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 1.0, + y: 0.0, + z: 0.0, + ), + y_axis: ( + x: 0.0, + y: 1.0, + z: 0.0, + ), + z_axis: ( + x: 0.0, + y: 0.0, + z: 1.0, + ), + ), + translation: ( + x: -33.93416, + y: 5.6987834, + z: -33.676632, + ), + )), + "bevy_core::name::Name": ( + hash: 12227652970378347066, + name: "SubParentingTest", + ), + "bevy_hierarchy::components::parent::Parent": (17179869213), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Sphero"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 4294967323: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 1.4628916, + y: 2.0, + z: -5.380514, + ), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 1.0, + y: 0.0, + z: 0.0, + ), + y_axis: ( + x: 0.0, + y: 1.0, + z: 0.0, + ), + z_axis: ( + x: 0.0, + y: 0.0, + z: 1.0, + ), + ), + translation: ( + x: -20.334751, + y: 5.6987834, + z: -35.06186, + ), + )), + "bevy_core::name::Name": ( + hash: 12227652970378347066, + name: "SubParentingTest", + ), + "bevy_hierarchy::components::parent::Parent": (4294967319), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Sphero"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 4294967326: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 5.1854753, + y: 2.0, + z: 0.4241066, + ), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 1.0, + y: 0.0, + z: 0.0, + ), + y_axis: ( + x: 0.0, + y: 1.0, + z: 0.0, + ), + z_axis: ( + x: 0.0, + y: 0.0, + z: 1.0, + ), + ), + translation: ( + x: -18.075058, + y: 3.6987834, + z: -23.876724, + ), + )), + "bevy_core::name::Name": ( + hash: 11525084598183387914, + name: "ParentingTest", + ), + "bevy_hierarchy::components::parent::Parent": (4294967355), + "bevy_hierarchy::components::children::Children": ([ + 4294967331, + ]), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Container"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 4294967327: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 2.0302448, + y: 2.0, + z: 0.7366843, + ), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 1.0, + y: 0.0, + z: 0.0, + ), + y_axis: ( + x: 0.0, + y: 1.0, + z: 0.0, + ), + z_axis: ( + x: 0.0, + y: 0.0, + z: 1.0, + ), + ), + translation: ( + x: -19.200045, + y: 5.6987834, + z: -22.827461, + ), + )), + "bevy_core::name::Name": ( + hash: 12227652970378347066, + name: "SubParentingTest", + ), + "bevy_hierarchy::components::parent::Parent": (8589934599), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Sphero"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 4294967328: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -1.0132551, + y: 2.0, + z: -2.93677, + ), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 1.0, + y: 0.0, + z: 0.0, + ), + y_axis: ( + x: 0.0, + y: 1.0, + z: 0.0, + ), + z_axis: ( + x: 0.0, + y: 0.0, + z: 1.0, + ), + ), + translation: ( + x: -25.287045, + y: 5.6987834, + z: -30.17437, + ), + )), + "bevy_core::name::Name": ( + hash: 12227652970378347066, + name: "SubParentingTest", + ), + "bevy_hierarchy::components::parent::Parent": (4294967315), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Sphero"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 4294967329: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -4.2068176, + y: 2.0, + z: 3.3114986, + ), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 1.0, + y: 0.0, + z: 0.0, + ), + y_axis: ( + x: 0.0, + y: 1.0, + z: 0.0, + ), + z_axis: ( + x: 0.0, + y: 0.0, + z: 1.0, + ), + ), + translation: ( + x: -27.467352, + y: 3.6987834, + z: -20.989332, + ), + )), + "bevy_core::name::Name": ( + hash: 11525084598183387914, + name: "ParentingTest", + ), + "bevy_hierarchy::components::parent::Parent": (4294967355), + "bevy_hierarchy::components::children::Children": ([ + 4294967314, + ]), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Container"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 4294967330: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -5.2756443, + y: 2.0, + z: 3.7743902, + ), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 1.0, + y: 0.0, + z: 0.0, + ), + y_axis: ( + x: 0.0, + y: 1.0, + z: 0.0, + ), + z_axis: ( + x: 0.0, + y: 0.0, + z: 1.0, + ), + ), + translation: ( + x: -28.536179, + y: 3.6987834, + z: -20.52644, + ), + )), + "bevy_core::name::Name": ( + hash: 11525084598183387914, + name: "ParentingTest", + ), + "bevy_hierarchy::components::parent::Parent": (4294967355), + "bevy_hierarchy::components::children::Children": ([ + 21474836496, + ]), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Container"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 4294967331: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 5.1854753, + y: 2.0, + z: 0.4241066, + ), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 1.0, + y: 0.0, + z: 0.0, + ), + y_axis: ( + x: 0.0, + y: 1.0, + z: 0.0, + ), + z_axis: ( + x: 0.0, + y: 0.0, + z: 1.0, + ), + ), + translation: ( + x: -12.889583, + y: 5.6987834, + z: -23.452618, + ), + )), + "bevy_core::name::Name": ( + hash: 12227652970378347066, + name: "SubParentingTest", + ), + "bevy_hierarchy::components::parent::Parent": (4294967326), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Sphero"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 8589934599: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 2.0302448, + y: 2.0, + z: 0.7366843, + ), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 1.0, + y: 0.0, + z: 0.0, + ), + y_axis: ( + x: 0.0, + y: 1.0, + z: 0.0, + ), + z_axis: ( + x: 0.0, + y: 0.0, + z: 1.0, + ), + ), + translation: ( + x: -21.23029, + y: 3.6987834, + z: -23.564146, + ), + )), + "bevy_core::name::Name": ( + hash: 11525084598183387914, + name: "ParentingTest", + ), + "bevy_hierarchy::components::parent::Parent": (4294967355), + "bevy_hierarchy::components::children::Children": ([ + 4294967327, + ]), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Container"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 12884901893: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -5.375821, + y: 2.0, + z: -1.2236366, + ), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 1.0, + y: 0.0, + z: 0.0, + ), + y_axis: ( + x: 0.0, + y: 1.0, + z: 0.0, + ), + z_axis: ( + x: 0.0, + y: 0.0, + z: 1.0, + ), + ), + translation: ( + x: -28.636356, + y: 3.6987834, + z: -25.524467, + ), + )), + "bevy_core::name::Name": ( + hash: 11525084598183387914, + name: "ParentingTest", + ), + "bevy_hierarchy::components::parent::Parent": (4294967355), + "bevy_hierarchy::components::children::Children": ([ + 4294967316, + ]), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Container"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 17179869209: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -2.2050228, + y: 2.0, + z: 5.3109236, + ), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 1.0, + y: 0.0, + z: 0.0, + ), + y_axis: ( + x: 0.0, + y: 1.0, + z: 0.0, + ), + z_axis: ( + x: 0.0, + y: 0.0, + z: 1.0, + ), + ), + translation: ( + x: -25.465557, + y: 3.6987834, + z: -18.989906, + ), + )), + "bevy_core::name::Name": ( + hash: 11525084598183387914, + name: "ParentingTest", + ), + "bevy_hierarchy::components::parent::Parent": (4294967355), + "bevy_hierarchy::components::children::Children": ([ + 4294967313, + ]), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Container"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 17179869213: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -5.3368125, + y: 2.0, + z: -4.6879, + ), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 1.0, + y: 0.0, + z: 0.0, + ), + y_axis: ( + x: 0.0, + y: 1.0, + z: 0.0, + ), + z_axis: ( + x: 0.0, + y: 0.0, + z: 1.0, + ), + ), + translation: ( + x: -28.597347, + y: 3.6987834, + z: -28.988731, + ), + )), + "bevy_core::name::Name": ( + hash: 11525084598183387914, + name: "ParentingTest", + ), + "bevy_hierarchy::components::parent::Parent": (4294967355), + "bevy_hierarchy::components::children::Children": ([ + 4294967322, + ]), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Container"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 21474836496: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -5.2756443, + y: 2.0, + z: 3.7743902, + ), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 1.0, + y: 0.0, + z: 0.0, + ), + y_axis: ( + x: 0.0, + y: 1.0, + z: 0.0, + ), + z_axis: ( + x: 0.0, + y: 0.0, + z: 1.0, + ), + ), + translation: ( + x: -33.81182, + y: 5.6987834, + z: -16.752048, + ), + )), + "bevy_core::name::Name": ( + hash: 12227652970378347066, + name: "SubParentingTest", + ), + "bevy_hierarchy::components::parent::Parent": (4294967330), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Sphero"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 4294967306: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -7.288043, + y: 0.4981097, + z: 2.2298644, + ), + rotation: ( + x: 0.6357745, + y: 0.5369656, + z: 0.30949104, + w: -0.46008036, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 0.23176646, + y: 0.39799657, + z: 0.8876277, + ), + y_axis: ( + x: 0.9675596, + y: 0.000012218952, + z: -0.2526426, + ), + z_axis: ( + x: -0.10056165, + y: 0.9173868, + z: -0.38508248, + ), + ), + translation: ( + x: -7.288043, + y: 0.4981097, + z: 2.2298644, + ), + )), + "bevy_core::name::Name": ( + hash: 8887819794052303637, + name: "test11239097852357851377", + ), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_gltf_save_load_basic_example::game::picking::Pickable": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: 0.0, + y: 0.0, + z: 0.0, + ), + angvel: ( + x: 0.0, + y: 0.0, + z: 0.0, + ), + ), + }, + ), + 4294967307: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -6.933158, + y: 0.49766284, + z: 3.7499082, + ), + rotation: ( + x: -0.4469943, + y: 0.6741115, + z: 0.5477982, + w: 0.21374497, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: -0.5090184, + y: -0.36846972, + z: -0.77790123, + ), + y_axis: ( + x: -0.8368262, + y: 0.00022637844, + z: 0.54746854, + ), + z_axis: ( + x: -0.20154947, + y: 0.9296397, + z: -0.30846035, + ), + ), + translation: ( + x: -6.933158, + y: 0.49766284, + z: 3.7499082, + ), + )), + "bevy_core::name::Name": ( + hash: 12992148551202274181, + name: "test312134675716618292", + ), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_gltf_save_load_basic_example::game::picking::Pickable": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: -0.22741863, + y: -0.005733113, + z: -0.34727955, + ), + angvel: ( + x: -0.6965903, + y: 0.00006836644, + z: 0.455849, + ), + ), + }, + ), + 4294967355: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -23.260534, + y: 1.6987833, + z: -24.30083, + ), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 1.0, + y: 0.0, + z: 0.0, + ), + y_axis: ( + x: 0.0, + y: 1.0, + z: 0.0, + ), + z_axis: ( + x: 0.0, + y: 0.0, + z: 1.0, + ), + ), + translation: ( + x: -23.260534, + y: 1.6987833, + z: -24.30083, + ), + )), + "bevy_core::name::Name": ( + hash: 3742511300108809551, + name: "Player", + ), + "bevy_hierarchy::components::children::Children": ([ + 4294967319, + 8589934599, + 4294967309, + 4294967329, + 12884901893, + 4294967330, + 4294967326, + 17179869209, + 4294967315, + 17179869213, + ]), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Player"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 4294967356: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -1.3321748, + y: 0.49812344, + z: 5.8811045, + ), + rotation: ( + x: 0.69376576, + y: 0.61800337, + z: -0.13670437, + w: 0.34361714, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 0.19876748, + y: 0.76355124, + z: -0.6143947, + ), + y_axis: ( + x: 0.9514471, + y: 0.0000019669533, + z: 0.3078121, + ), + z_axis: ( + x: 0.23503149, + y: -0.6457471, + z: -0.7264782, + ), + ), + translation: ( + x: -1.3321748, + y: 0.49812344, + z: 5.8811045, + ), + )), + "bevy_core::name::Name": ( + hash: 11771294558428819596, + name: "test18243995066128479105", + ), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_gltf_save_load_basic_example::game::picking::Pickable": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: -0.18925886, + y: -0.00008480358, + z: 0.58490133, + ), + angvel: ( + x: 1.1719902, + y: 0.0000011311529, + z: 0.37922552, + ), + ), + }, + ), + 4294967357: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 6.535548, + y: 0.49754578, + z: 8.382693, + ), + rotation: ( + x: -0.17711681, + y: 0.07649447, + z: -0.68457824, + w: 0.7029444, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 0.051002443, + y: -0.9895378, + z: 0.13495791, + ), + y_axis: ( + x: 0.9353439, + y: -0.00003540516, + z: -0.35373944, + ), + z_axis: ( + x: 0.35004336, + y: 0.14427364, + z: 0.9255565, + ), + ), + translation: ( + x: 6.535548, + y: 0.49754578, + z: 8.382693, + ), + )), + "bevy_core::name::Name": ( + hash: 14649876376422140665, + name: "Health_Pickup", + ), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_gltf_save_load_basic_example::game::picking::Pickable": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 4294967358: ( + components: { + "bevy_render::camera::camera::Camera": ( + viewport: None, + order: 0, + is_active: true, + hdr: true, + msaa_writeback: true, + ), + "bevy_render::camera::camera::CameraRenderGraph": ("core_3d"), + "bevy_render::camera::projection::Projection": Perspective(( + fov: 0.3995965, + aspect_ratio: 1.7777778, + near: 0.1, + far: 100.0, + )), + "bevy_render::view::visibility::VisibleEntities": (), + "bevy_render::primitives::Frustum": (), + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 2.7393978, + y: 49.69877, + z: -50.30073, + ), + rotation: ( + x: -0.16939682, + y: 0.8284353, + z: 0.40895978, + w: 0.34314942, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: -0.70710635, + y: 0.0, + z: -0.7071071, + ), + y_axis: ( + x: -0.56133723, + y: 0.6081132, + z: 0.5613366, + ), + z_axis: ( + x: 0.4300012, + y: 0.79385024, + z: -0.43000066, + ), + ), + translation: ( + x: 2.7393978, + y: 49.69877, + z: -50.30073, + ), + )), + "bevy_core_pipeline::core_3d::camera_3d::Camera3d": ( + clear_color: Default, + depth_load_op: Clear(0.0), + depth_texture_usages: (16), + screen_space_specular_transmission_steps: 1, + screen_space_specular_transmission_quality: Medium, + ), + "bevy_core_pipeline::tonemapping::Tonemapping": BlenderFilmic, + "bevy_core::name::Name": ( + hash: 15844466280752129540, + name: "Camera", + ), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_save_load_basic_example::core::camera::camera_tracking::CameraTrackingOffset": (( + x: 26.0, + y: 48.0, + z: -26.0, + )), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 4294967361: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -25.493845, + y: 0.49812365, + z: -12.402787, + ), + rotation: ( + x: 0.36418545, + y: -0.7049958, + z: -0.6061089, + w: -0.05460626, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: -0.7287742, + y: -0.44730374, + z: -0.5184665, + ), + y_axis: ( + x: -0.5796931, + y: 0.0000019073486, + z: 0.8148349, + ), + z_axis: ( + x: -0.36447772, + y: 0.89438206, + z: -0.25930023, + ), + ), + translation: ( + x: -25.493845, + y: 0.49812365, + z: -12.402787, + ), + )), + "bevy_core::name::Name": ( + hash: 5247816456120987171, + name: "test2582167838114257347", + ), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_gltf_save_load_basic_example::game::picking::Pickable": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: -1.751566, + y: -0.00006749275, + z: -1.2461432, + ), + angvel: ( + x: -2.4969466, + y: 0.000004362554, + z: 3.5096824, + ), + ), + }, + ), + 4294967492: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -4.9920077, + y: 0.81558603, + z: 4.474934, + ), + rotation: ( + x: -0.063114665, + y: -0.26704848, + z: 0.59302473, + w: 0.756983, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 0.15401351, + y: 0.9315286, + z: 0.32944518, + ), + y_axis: ( + x: -0.86410993, + y: 0.28867638, + z: -0.41228616, + ), + z_axis: ( + x: -0.4791594, + y: -0.22117925, + z: 0.8494033, + ), + ), + translation: ( + x: -4.9920077, + y: 0.81558603, + z: 4.474934, + ), + )), + "bevy_core::name::Name": ( + hash: 5889033640271354729, + name: "test7607124372052964197", + ), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_gltf_save_load_basic_example::game::picking::Pickable": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: -0.58648634, + y: -0.31429863, + z: 0.66094667, + ), + angvel: ( + x: 1.5286787, + y: -0.62694865, + z: 1.0580964, + ), + ), + }, + ), + 4294967496: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 3.2762408, + y: 0.4981076, + z: 2.1016297, + ), + rotation: ( + x: -0.654017, + y: -0.00000071709474, + z: -0.26901913, + w: 0.70702934, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 0.8552574, + y: -0.3804079, + z: 0.35188717, + ), + y_axis: ( + x: 0.38040975, + y: -0.00021898746, + z: -0.92481804, + ), + z_axis: ( + x: 0.35188514, + y: 0.92481875, + z: 0.14452362, + ), + ), + translation: ( + x: 3.2762408, + y: 0.4981076, + z: 2.1016297, + ), + )), + "bevy_core::name::Name": ( + hash: 15406684986167072, + name: "test5726861144769665045", + ), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_gltf_save_load_basic_example::game::picking::Pickable": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: 0.108614616, + y: 0.0010768953, + z: -0.26639044, + ), + angvel: ( + x: 0.0011672559, + y: -0.0024434621, + z: -0.003079506, + ), + ), + }, + ), + 4294967500: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 1.0343102, + y: 0.55711305, + z: 3.023412, + ), + rotation: ( + x: 0.475089, + y: 0.000006328337, + z: 0.49772972, + w: 0.7256414, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 0.50453025, + y: 0.7223526, + z: 0.47292265, + ), + y_axis: ( + x: -0.7223406, + y: 0.053111076, + z: 0.68949485, + ), + z_axis: ( + x: 0.472941, + y: -0.6894822, + z: 0.5485809, + ), + ), + translation: ( + x: 1.0343102, + y: 0.55711305, + z: 3.023412, + ), + )), + "bevy_core::name::Name": ( + hash: 14907442938390545608, + name: "test15491425733849030141", + ), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_gltf_save_load_basic_example::game::picking::Pickable": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: -1.4335963, + y: -3.1702905, + z: 1.3684047, + ), + angvel: ( + x: 2.0015316, + y: 0.000009884588, + z: 2.096909, + ), + ), + }, + ), + 4294967504: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 6.341096, + y: 0.9969703, + z: 7.157166, + ), + rotation: ( + x: 0.40568426, + y: -0.35131618, + z: -0.32982755, + w: 0.7766666, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 0.53558147, + y: -0.797379, + z: 0.27809942, + ), + y_axis: ( + x: 0.22728518, + y: 0.45326817, + z: 0.86191034, + ), + z_axis: ( + x: -0.8133228, + y: -0.39841533, + z: 0.42399442, + ), + ), + translation: ( + x: 6.341096, + y: 0.9969703, + z: 7.157166, + ), + )), + "bevy_core::name::Name": ( + hash: 5766952398097986970, + name: "test1319791526494826970", + ), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_gltf_save_load_basic_example::game::picking::Pickable": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: 0.23741817, + y: 0.0433962, + z: 0.27994156, + ), + angvel: ( + x: -0.105580404, + y: -0.11032705, + z: -0.22751106, + ), + ), + }, + ), + 4294967508: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 1.7111174, + y: 1.5965495, + z: -0.28478992, + ), + rotation: ( + x: 0.023569103, + y: -0.000000040367013, + z: 0.015840217, + w: 0.9995967, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 0.9994982, + y: 0.031667653, + z: 0.0007467601, + ), + y_axis: ( + x: -0.03166766, + y: 0.99838716, + z: 0.047119197, + ), + z_axis: ( + x: 0.00074659876, + y: -0.047119197, + z: 0.99888897, + ), + ), + translation: ( + x: 1.7111174, + y: 1.5965495, + z: -0.28478992, + ), + )), + "bevy_core::name::Name": ( + hash: 5571590536334849636, + name: "test1674739915643664398", + ), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_gltf_save_load_basic_example::game::picking::Pickable": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: -0.09296543, + y: -0.0063177478, + z: 0.13832632, + ), + angvel: ( + x: 0.08658932, + y: -0.00000014938985, + z: 0.05819437, + ), + ), + }, + ), + 4294967512: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -1.5826693, + y: 1.5393476, + z: -4.3061185, + ), + rotation: ( + x: 0.058324553, + y: 0.00000007249764, + z: -0.15305068, + w: 0.9864957, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 0.953151, + y: -0.30196765, + z: -0.017853368, + ), + y_axis: ( + x: 0.30196765, + y: 0.9463475, + z: 0.115073815, + ), + z_axis: ( + x: -0.017853081, + y: -0.11507386, + z: 0.9931965, + ), + ), + translation: ( + x: -1.5826693, + y: 1.5393476, + z: -4.3061185, + ), + )), + "bevy_core::name::Name": ( + hash: 9232722840751228859, + name: "test16789708871576306607", + ), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_gltf_save_load_basic_example::game::picking::Pickable": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: 0.92234856, + y: -0.22055921, + z: 0.35148948, + ), + angvel: ( + x: 0.2276644, + y: 0.0000009784032, + z: -0.5974168, + ), + ), + }, + ), + 4294967516: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -4.525213, + y: 0.7750231, + z: -0.17502502, + ), + rotation: ( + x: 0.4466202, + y: 0.17568037, + z: -0.6523265, + w: 0.58663183, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 0.08721304, + y: -0.6084262, + z: -0.7888038, + ), + y_axis: ( + x: 0.9222758, + y: -0.24999905, + z: 0.29480135, + ), + z_axis: ( + x: -0.376565, + y: -0.7532052, + z: 0.5393336, + ), + ), + translation: ( + x: -4.525213, + y: 0.7750231, + z: -0.17502502, + ), + )), + "bevy_core::name::Name": ( + hash: 333871819790518369, + name: "test15444470665606374897", + ), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_gltf_save_load_basic_example::game::picking::Pickable": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: -2.7437484, + y: -3.3437164, + z: -2.3115048, + ), + angvel: ( + x: -3.0006366, + y: 0.9244151, + z: 2.3713787, + ), + ), + }, + ), + 4294967520: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 1.8888425, + y: 1.5689882, + z: -1.5552188, + ), + rotation: ( + x: 0.113464825, + y: -0.00000020895095, + z: 0.021926481, + w: 0.9933, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 0.99903846, + y: 0.0435591, + z: 0.0049761836, + ), + y_axis: ( + x: -0.043559197, + y: 0.9732899, + z: 0.22540921, + ), + z_axis: ( + x: 0.004975354, + y: -0.22540924, + z: 0.97425145, + ), + ), + translation: ( + x: 1.8888425, + y: 1.5689882, + z: -1.5552188, + ), + )), + "bevy_core::name::Name": ( + hash: 3509298148931429876, + name: "test2496051646124357008", + ), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_gltf_save_load_basic_example::game::picking::Pickable": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: -0.16054136, + y: -0.13092233, + z: 0.83077115, + ), + angvel: ( + x: 0.5284708, + y: -0.0000013110855, + z: 0.10212349, + ), + ), + }, + ), + 4294967524: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -2.1701968, + y: 0.7805994, + z: 3.9727407, + ), + rotation: ( + x: 0.5706888, + y: 0.35842514, + z: -0.2145796, + w: 0.7069662, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 0.65097404, + y: 0.10569736, + z: -0.7517052, + ), + y_axis: ( + x: 0.7124995, + y: 0.25653982, + z: 0.65309393, + ), + z_axis: ( + x: 0.2618726, + y: -0.9607369, + z: 0.091691494, + ), + ), + translation: ( + x: -2.1701968, + y: 0.7805994, + z: 3.9727407, + ), + )), + "bevy_core::name::Name": ( + hash: 14209915335791442119, + name: "test15495879109551239256", + ), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_gltf_save_load_basic_example::game::picking::Pickable": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: 0.82487506, + y: -2.3229113, + z: 1.2957921, + ), + angvel: ( + x: 2.534364, + y: 0.839257, + z: -0.6552327, + ), + ), + }, + ), + 4294967528: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 5.063053, + y: 1.5967827, + z: -0.17321607, + ), + rotation: ( + x: 0.01903159, + y: 0.00000003883906, + z: 0.018831024, + w: 0.9996415, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 0.99929076, + y: 0.037648544, + z: 0.000716691, + ), + y_axis: ( + x: -0.037648544, + y: 0.9985664, + z: 0.038049534, + ), + z_axis: ( + x: 0.0007168463, + y: -0.038049534, + z: 0.9992756, + ), + ), + translation: ( + x: 5.063053, + y: 1.5967827, + z: -0.17321607, + ), + )), + "bevy_core::name::Name": ( + hash: 16108438029532537403, + name: "test13576182779630349751", + ), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_gltf_save_load_basic_example::game::picking::Pickable": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: -0.45847216, + y: -0.020973437, + z: 0.46335497, + ), + angvel: ( + x: 0.28996477, + y: 0.00000058175317, + z: 0.28690916, + ), + ), + }, + ), + 8589934658: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 6.1006203, + y: 0.49812356, + z: 3.580489, + ), + rotation: ( + x: 0.0768257, + y: -0.49943498, + z: -0.70292014, + w: 0.50056535, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: -0.487064, + y: -0.7804538, + z: 0.39199504, + ), + y_axis: ( + x: 0.626976, + y: 0.0000021457672, + z: 0.77903837, + ), + z_axis: ( + x: -0.6080044, + y: 0.6252132, + z: 0.48932505, + ), + ), + translation: ( + x: 6.1006203, + y: 0.49812356, + z: 3.580489, + ), + )), + "bevy_core::name::Name": ( + hash: 5610645830027884689, + name: "test18152638408579826398", + ), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_gltf_save_load_basic_example::game::picking::Pickable": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: 0.6629742, + y: -0.00007954295, + z: -0.5336061, + ), + angvel: ( + x: -1.0692078, + y: -0.0000014726246, + z: -1.3284277, + ), + ), + }, + ), + 17179869242: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -1.2373765, + y: 1.5983436, + z: -14.737572, + ), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 1.0, + y: 0.0, + z: 0.0, + ), + y_axis: ( + x: 0.0, + y: 1.0, + z: 0.0, + ), + z_axis: ( + x: 0.0, + y: 0.0, + z: 1.0, + ), + ), + translation: ( + x: -1.2373765, + y: 1.5983436, + z: -14.737572, + ), + )), + "bevy_core::name::Name": ( + hash: 15782802592076175270, + name: "Health_Pickup.001", + ), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_gltf_save_load_basic_example::game::picking::Pickable": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + }, + ), + 25769803903: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -7.389497, + y: 0.49812272, + z: -2.7608006, + ), + rotation: ( + x: -0.061189484, + y: -0.00019909017, + z: 0.7044531, + w: 0.7071079, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 0.0074915886, + y: 0.9962731, + z: -0.085928686, + ), + y_axis: ( + x: -0.99622434, + y: 0.00000333786, + z: -0.08681563, + ), + z_axis: ( + x: -0.0864918, + y: 0.086254634, + z: 0.99251163, + ), + ), + translation: ( + x: -7.389497, + y: 0.49812272, + z: -2.7608006, + ), + )), + "bevy_core::name::Name": ( + hash: 14711562368185091299, + name: "test3121563718590801717", + ), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_gltf_save_load_basic_example::game::picking::Pickable": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: -0.000011626149, + y: -0.00008378431, + z: 0.00048400648, + ), + angvel: ( + x: 0.00096944225, + y: -0.00000017537552, + z: 0.000023260392, + ), + ), + }, + ), + 42949673007: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 1.2928966, + y: 0.49812183, + z: 5.187275, + ), + rotation: ( + x: -0.6987065, + y: -0.0073659495, + z: -0.10865899, + w: -0.7070701, + ), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_transform::components::global_transform::GlobalTransform": (( + matrix3: ( + x_axis: ( + x: 0.97627795, + y: 0.16395232, + z: 0.14142501, + ), + y_axis: ( + x: -0.14336577, + y: 0.000004887581, + z: 0.98966974, + ), + z_axis: ( + x: 0.16225797, + y: -0.98646826, + z: 0.02350992, + ), + ), + translation: ( + x: 1.2928966, + y: 0.49812183, + z: 5.187275, + ), + )), + "bevy_core::name::Name": ( + hash: 5172269147847631123, + name: "test5254630037543835847", + ), + "bevy_gltf_save_load::saveable::Dynamic": (true), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": (), + "bevy_gltf_save_load_basic_example::game::picking::Pickable": (), + "bevy_render::view::visibility::InheritedVisibility": (true), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: 2.49349, + y: -0.00012572497, + z: 0.3612286, + ), + angvel: ( + x: 0.7238101, + y: -0.00003163344, + z: -4.996319, + ), + ), + }, + ), + }, +) \ No newline at end of file diff --git a/examples/bevy_gltf_save_load/basic/src/assets/assets_core.rs b/examples/bevy_gltf_save_load/basic/src/assets/assets_core.rs new file mode 100644 index 00000000..29b577a3 --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/assets/assets_core.rs @@ -0,0 +1,5 @@ +use bevy::prelude::*; +use bevy_asset_loader::prelude::*; + +#[derive(AssetCollection, Resource)] +pub struct CoreAssets {} diff --git a/examples/bevy_gltf_save_load/basic/src/assets/assets_game.rs b/examples/bevy_gltf_save_load/basic/src/assets/assets_game.rs new file mode 100644 index 00000000..05f2af6d --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/assets/assets_game.rs @@ -0,0 +1,16 @@ +use bevy::gltf::Gltf; +use bevy::prelude::*; +use bevy::utils::HashMap; +use bevy_asset_loader::prelude::*; + +#[derive(AssetCollection, Resource)] +pub struct GameAssets { + #[asset(key = "world")] + pub world: Handle, + + #[asset(key = "world_dynamic")] + pub world_dynamic: Handle, + + #[asset(key = "models", collection(typed, mapped))] + pub models: HashMap>, +} diff --git a/examples/bevy_gltf_save_load/basic/src/assets/mod.rs b/examples/bevy_gltf_save_load/basic/src/assets/mod.rs new file mode 100644 index 00000000..a2c8b22a --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/assets/mod.rs @@ -0,0 +1,35 @@ +pub mod assets_core; +pub use assets_core::*; + +pub mod assets_game; +pub use assets_game::*; + +use bevy::prelude::*; +use bevy_asset_loader::prelude::*; + +use crate::state::AppState; + +pub struct AssetsPlugin; +impl Plugin for AssetsPlugin { + fn build(&self, app: &mut App) { + app + // load core assets (ie assets needed in the main menu, and everywhere else before loading more assets in game) + .add_loading_state( + LoadingState::new(AppState::CoreLoading).continue_to_state(AppState::MenuRunning), + ) + .add_dynamic_collection_to_loading_state::<_, StandardDynamicAssetCollection>( + AppState::CoreLoading, + "assets_core.assets.ron", + ) + .add_collection_to_loading_state::<_, CoreAssets>(AppState::CoreLoading) + // load game assets + .add_loading_state( + LoadingState::new(AppState::AppLoading).continue_to_state(AppState::AppRunning), + ) + .add_dynamic_collection_to_loading_state::<_, StandardDynamicAssetCollection>( + AppState::AppLoading, + "assets_game.assets.ron", + ) + .add_collection_to_loading_state::<_, GameAssets>(AppState::AppLoading); + } +} diff --git a/examples/bevy_gltf_save_load/basic/src/core/camera/camera_replace_proxies.rs b/examples/bevy_gltf_save_load/basic/src/core/camera/camera_replace_proxies.rs new file mode 100644 index 00000000..9055c95b --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/core/camera/camera_replace_proxies.rs @@ -0,0 +1,24 @@ +use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings}; +use bevy::core_pipeline::tonemapping::{DebandDither, Tonemapping}; +use bevy::prelude::*; + +use super::CameraTrackingOffset; + +pub fn camera_replace_proxies( + mut commands: Commands, + mut added_cameras: Query<(Entity, &mut Camera), (Added, With)>, +) { + for (entity, mut camera) in added_cameras.iter_mut() { + info!("detected added camera, updating proxy"); + camera.hdr = true; + commands + .entity(entity) + .insert(DebandDither::Enabled) + .insert(Tonemapping::BlenderFilmic) + .insert(BloomSettings { + intensity: 0.01, + composite_mode: BloomCompositeMode::Additive, + ..default() + }); + } +} diff --git a/examples/bevy_gltf_save_load/basic/src/core/camera/camera_tracking.rs b/examples/bevy_gltf_save_load/basic/src/core/camera/camera_tracking.rs new file mode 100644 index 00000000..62da84d7 --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/core/camera/camera_tracking.rs @@ -0,0 +1,58 @@ +use bevy::prelude::*; + +#[derive(Component, Reflect, Debug)] +#[reflect(Component)] +/// Component for cameras, with an offset from the Trackable target +/// +pub struct CameraTracking { + pub offset: Vec3, +} +impl Default for CameraTracking { + fn default() -> Self { + CameraTracking { + offset: Vec3::new(0.0, 6.0, 8.0), + } + } +} + +#[derive(Component, Reflect, Debug, Deref, DerefMut)] +#[reflect(Component)] +/// Component for cameras, with an offset from the Trackable target +pub struct CameraTrackingOffset(Vec3); +impl Default for CameraTrackingOffset { + fn default() -> Self { + CameraTrackingOffset(Vec3::new(0.0, 6.0, 8.0)) + } +} + +impl CameraTrackingOffset { + fn new(input: Vec3) -> Self { + CameraTrackingOffset(input) + } +} + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +/// Add this component to an entity if you want it to be tracked by a Camera +pub struct CameraTrackable; + +pub fn camera_track( + mut tracking_cameras: Query< + (&mut Transform, &CameraTrackingOffset), + ( + With, + With, + Without, + ), + >, + camera_tracked: Query<&Transform, With>, +) { + for (mut camera_transform, tracking_offset) in tracking_cameras.iter_mut() { + for tracked_transform in camera_tracked.iter() { + let target_position = tracked_transform.translation + tracking_offset.0; + let eased_position = camera_transform.translation.lerp(target_position, 0.1); + camera_transform.translation = eased_position; // + tracking.offset;// tracked_transform.translation + tracking.offset; + *camera_transform = camera_transform.looking_at(tracked_transform.translation, Vec3::Y); + } + } +} diff --git a/examples/bevy_gltf_save_load/basic/src/core/camera/mod.rs b/examples/bevy_gltf_save_load/basic/src/core/camera/mod.rs new file mode 100644 index 00000000..a6bbb65d --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/core/camera/mod.rs @@ -0,0 +1,24 @@ +pub mod camera_tracking; +pub use camera_tracking::*; + +pub mod camera_replace_proxies; +pub use camera_replace_proxies::*; + +use bevy::prelude::*; +use bevy_gltf_blueprints::GltfBlueprintsSet; + +pub struct CameraPlugin; +impl Plugin for CameraPlugin { + fn build(&self, app: &mut App) { + app.register_type::() + .register_type::() + .register_type::() + .add_systems( + Update, + ( + camera_replace_proxies.after(GltfBlueprintsSet::AfterSpawn), + camera_track, + ), + ); + } +} diff --git a/examples/bevy_gltf_save_load/basic/src/core/lighting/lighting_replace_proxies.rs b/examples/bevy_gltf_save_load/basic/src/core/lighting/lighting_replace_proxies.rs new file mode 100644 index 00000000..48c09087 --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/core/lighting/lighting_replace_proxies.rs @@ -0,0 +1,25 @@ +use bevy::prelude::*; + +use bevy::pbr::{CascadeShadowConfig, CascadeShadowConfigBuilder}; + +// fixme might be too specific to might needs, should it be moved out ? also these are all for lights, not models +pub fn lighting_replace_proxies( + mut added_dirights: Query<(Entity, &mut DirectionalLight), Added>, + mut added_spotlights: Query<&mut SpotLight, Added>, + mut commands: Commands, +) { + for (entity, mut light) in added_dirights.iter_mut() { + light.illuminance *= 5.0; + light.shadows_enabled = true; + let shadow_config: CascadeShadowConfig = CascadeShadowConfigBuilder { + first_cascade_far_bound: 15.0, + maximum_distance: 135.0, + ..default() + } + .into(); + commands.entity(entity).insert(shadow_config); + } + for mut light in added_spotlights.iter_mut() { + light.shadows_enabled = true; + } +} diff --git a/examples/bevy_gltf_save_load/basic/src/core/lighting/mod.rs b/examples/bevy_gltf_save_load/basic/src/core/lighting/mod.rs new file mode 100644 index 00000000..c9688cda --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/core/lighting/mod.rs @@ -0,0 +1,18 @@ +mod lighting_replace_proxies; +use lighting_replace_proxies::*; + +use bevy::pbr::{DirectionalLightShadowMap, NotShadowCaster}; +use bevy::prelude::*; + +pub struct LightingPlugin; +impl Plugin for LightingPlugin { + fn build(&self, app: &mut App) { + app + .insert_resource(DirectionalLightShadowMap { size: 4096 }) + // FIXME: adding these since they are missing + .register_type::() + + .add_systems(PreUpdate, lighting_replace_proxies) // FIXME: you should actually run this in a specific state most likely + ; + } +} diff --git a/examples/bevy_gltf_save_load/basic/src/core/mod.rs b/examples/bevy_gltf_save_load/basic/src/core/mod.rs new file mode 100644 index 00000000..2a42178e --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/core/mod.rs @@ -0,0 +1,62 @@ +pub mod camera; +pub use camera::*; + +pub mod lighting; +pub use lighting::*; + +pub mod relationships; +pub use relationships::*; + +pub mod physics; +pub use physics::*; + +use bevy::{ + core_pipeline::tonemapping::Tonemapping, + prelude::*, + render::{camera::CameraRenderGraph, primitives::Frustum, view::VisibleEntities}, + utils::HashSet, +}; +use bevy_rapier3d::dynamics::Velocity; +use std::any::TypeId; + +use bevy_gltf_blueprints::*; +use bevy_gltf_save_load::*; + +use crate::game::Pickable; + +pub struct CorePlugin; +impl Plugin for CorePlugin { + fn build(&self, app: &mut App) { + app.add_plugins(( + LightingPlugin, + CameraPlugin, + PhysicsPlugin, + SaveLoadPlugin { + save_path: "scenes".into(), + component_filter: SceneFilter::Allowlist(HashSet::from([ + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + ])), + ..Default::default() + }, + BlueprintsPlugin { + library_folder: "models/library".into(), + format: GltfFormat::GLB, + aabbs: true, + ..Default::default() + }, + )); + } +} diff --git a/examples/bevy_gltf_save_load/basic/src/core/physics/controls.rs b/examples/bevy_gltf_save_load/basic/src/core/physics/controls.rs new file mode 100644 index 00000000..25625485 --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/core/physics/controls.rs @@ -0,0 +1,26 @@ +use bevy::{ + ecs::system::Res, + input::{keyboard::KeyCode, Input}, + log::info, + prelude::ResMut, +}; +use bevy_rapier3d::{prelude::RapierConfiguration, render::DebugRenderContext}; + +pub fn pause_physics(mut physics_config: ResMut) { + info!("pausing physics"); + physics_config.physics_pipeline_active = false; +} + +pub fn resume_physics(mut physics_config: ResMut) { + info!("unpausing physics"); + physics_config.physics_pipeline_active = true; +} + +pub fn toggle_physics_debug( + mut debug_config: ResMut, + keycode: Res>, +) { + if keycode.just_pressed(KeyCode::D) { + debug_config.enabled = !debug_config.enabled; + } +} diff --git a/examples/bevy_gltf_save_load/basic/src/core/physics/mod.rs b/examples/bevy_gltf_save_load/basic/src/core/physics/mod.rs new file mode 100644 index 00000000..ac1b0019 --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/core/physics/mod.rs @@ -0,0 +1,38 @@ +pub mod physics_replace_proxies; +use bevy_rapier3d::{ + prelude::{NoUserData, RapierPhysicsPlugin}, + render::RapierDebugRenderPlugin, +}; +pub use physics_replace_proxies::*; + +pub mod utils; + +pub mod controls; +pub use controls::*; + +use crate::state::GameState; +use bevy::prelude::*; +// use super::blueprints::GltfBlueprintsSet; +use bevy_gltf_blueprints::GltfBlueprintsSet; +// use crate::Collider; +pub struct PhysicsPlugin; +impl Plugin for PhysicsPlugin { + fn build(&self, app: &mut App) { + app.add_plugins(( + RapierPhysicsPlugin::::default(), + RapierDebugRenderPlugin::default(), + )) + .register_type::() + .register_type::() + // find a way to make serde's stuff serializable + // .register_type::() + //bevy_rapier3d::dynamics::CoefficientCombineRule + .add_systems( + Update, + physics_replace_proxies.after(GltfBlueprintsSet::AfterSpawn), + ) + .add_systems(OnEnter(GameState::InGame), resume_physics) + .add_systems(OnExit(GameState::InGame), pause_physics) + .add_systems(Update, toggle_physics_debug); + } +} diff --git a/examples/bevy_gltf_save_load/basic/src/core/physics/physics_replace_proxies.rs b/examples/bevy_gltf_save_load/basic/src/core/physics/physics_replace_proxies.rs new file mode 100644 index 00000000..b91462c7 --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/core/physics/physics_replace_proxies.rs @@ -0,0 +1,101 @@ +use bevy::prelude::*; +// use bevy::render::primitives::Aabb; +use bevy_rapier3d::geometry::Collider as RapierCollider; +use bevy_rapier3d::prelude::{ActiveCollisionTypes, ActiveEvents, ComputedColliderShape}; + +use super::utils::*; + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +pub enum Collider { + Ball(f32), + Cuboid(Vec3), + Capsule(Vec3, Vec3, f32), + #[default] + Mesh, +} + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +pub enum AutoAABBCollider { + #[default] + Cuboid, + Ball, + Capsule, +} + +// replaces all physics stand-ins with the actual rapier types +pub fn physics_replace_proxies( + meshes: Res>, + mesh_handles: Query<&Handle>, + mut proxy_colliders: Query< + (Entity, &Collider, &Name, &mut Visibility), + (Without, Added), + >, + // needed for tri meshes + children: Query<&Children>, + + mut commands: Commands, +) { + for proxy_colider in proxy_colliders.iter_mut() { + let (entity, collider_proxy, name, mut visibility) = proxy_colider; + // we hide the collider meshes: perhaps they should be removed altogether once processed ? + if name.ends_with("_collider") || name.ends_with("_sensor") { + *visibility = Visibility::Hidden; + } + + let mut rapier_collider: RapierCollider; + match collider_proxy { + Collider::Ball(radius) => { + info!("generating collider from proxy: ball"); + rapier_collider = RapierCollider::ball(*radius); + commands.entity(entity) + .insert(rapier_collider) + .insert(ActiveEvents::COLLISION_EVENTS) // FIXME: this is just for demo purposes !!! + ; + } + Collider::Cuboid(size) => { + info!("generating collider from proxy: cuboid"); + rapier_collider = RapierCollider::cuboid(size.x, size.y, size.z); + commands.entity(entity) + .insert(rapier_collider) + .insert(ActiveEvents::COLLISION_EVENTS) // FIXME: this is just for demo purposes !!! + ; + } + Collider::Capsule(a, b, radius) => { + info!("generating collider from proxy: capsule"); + rapier_collider = RapierCollider::capsule(*a, *b, *radius); + commands.entity(entity) + .insert(rapier_collider) + .insert(ActiveEvents::COLLISION_EVENTS) // FIXME: this is just for demo purposes !!! + ; + } + Collider::Mesh => { + info!("generating collider from proxy: mesh"); + for (_, collider_mesh) in + Mesh::search_in_children(entity, &children, &meshes, &mesh_handles) + { + rapier_collider = RapierCollider::from_bevy_mesh( + collider_mesh, + &ComputedColliderShape::TriMesh, + ) + .unwrap(); + commands + .entity(entity) + .insert(rapier_collider) + // FIXME: this is just for demo purposes !!! + .insert( + ActiveCollisionTypes::default() + | ActiveCollisionTypes::KINEMATIC_STATIC + | ActiveCollisionTypes::STATIC_STATIC + | ActiveCollisionTypes::DYNAMIC_STATIC, + ) + .insert(ActiveEvents::COLLISION_EVENTS); + // .insert(ActiveEvents::COLLISION_EVENTS) + // break; + // RapierCollider::convex_hull(points) + } + } + } + } +} diff --git a/examples/bevy_gltf_save_load/basic/src/core/physics/utils.rs b/examples/bevy_gltf_save_load/basic/src/core/physics/utils.rs new file mode 100644 index 00000000..7886710f --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/core/physics/utils.rs @@ -0,0 +1,175 @@ +use bevy::prelude::*; +use bevy::render::mesh::{MeshVertexAttributeId, PrimitiveTopology, VertexAttributeValues}; +// TAKEN VERBATIB FROM https://github.com/janhohenheim/foxtrot/blob/src/util/trait_extension.rs + +pub(crate) trait Vec3Ext: Copy { + fn is_approx_zero(self) -> bool; + fn split(self, up: Vec3) -> SplitVec3; +} +impl Vec3Ext for Vec3 { + #[inline] + fn is_approx_zero(self) -> bool { + self.length_squared() < 1e-5 + } + + #[inline] + fn split(self, up: Vec3) -> SplitVec3 { + let vertical = up * self.dot(up); + let horizontal = self - vertical; + SplitVec3 { + vertical, + horizontal, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) struct SplitVec3 { + pub(crate) vertical: Vec3, + pub(crate) horizontal: Vec3, +} + +pub(crate) trait Vec2Ext: Copy { + fn is_approx_zero(self) -> bool; + fn x0y(self) -> Vec3; +} +impl Vec2Ext for Vec2 { + #[inline] + fn is_approx_zero(self) -> bool { + self.length_squared() < 1e-5 + } + + #[inline] + fn x0y(self) -> Vec3 { + Vec3::new(self.x, 0., self.y) + } +} + +pub(crate) trait MeshExt { + fn transform(&mut self, transform: Transform); + fn transformed(&self, transform: Transform) -> Mesh; + fn read_coords_mut(&mut self, id: impl Into) -> &mut Vec<[f32; 3]>; + fn search_in_children<'a>( + parent: Entity, + children: &'a Query<&Children>, + meshes: &'a Assets, + mesh_handles: &'a Query<&Handle>, + ) -> Vec<(Entity, &'a Mesh)>; +} + +impl MeshExt for Mesh { + fn transform(&mut self, transform: Transform) { + for coords in self.read_coords_mut(Mesh::ATTRIBUTE_POSITION.clone()) { + let vec3 = (*coords).into(); + let transformed = transform.transform_point(vec3); + *coords = transformed.into(); + } + for normal in self.read_coords_mut(Mesh::ATTRIBUTE_NORMAL.clone()) { + let vec3 = (*normal).into(); + let transformed = transform.rotation.mul_vec3(vec3); + *normal = transformed.into(); + } + } + + fn transformed(&self, transform: Transform) -> Mesh { + let mut mesh = self.clone(); + mesh.transform(transform); + mesh + } + + fn read_coords_mut(&mut self, id: impl Into) -> &mut Vec<[f32; 3]> { + // Guaranteed by Bevy for the current usage + match self + .attribute_mut(id) + .expect("Failed to read unknown mesh attribute") + { + VertexAttributeValues::Float32x3(values) => values, + // Guaranteed by Bevy for the current usage + _ => unreachable!(), + } + } + + fn search_in_children<'a>( + parent: Entity, + children_query: &'a Query<&Children>, + meshes: &'a Assets, + mesh_handles: &'a Query<&Handle>, + ) -> Vec<(Entity, &'a Mesh)> { + if let Ok(children) = children_query.get(parent) { + let mut result: Vec<_> = children + .iter() + .filter_map(|entity| mesh_handles.get(*entity).ok().map(|mesh| (*entity, mesh))) + .map(|(entity, mesh_handle)| { + ( + entity, + meshes + .get(mesh_handle) + .expect("Failed to get mesh from handle"), + ) + }) + .map(|(entity, mesh)| { + assert_eq!(mesh.primitive_topology(), PrimitiveTopology::TriangleList); + (entity, mesh) + }) + .collect(); + let mut inner_result = children + .iter() + .flat_map(|entity| { + Self::search_in_children(*entity, children_query, meshes, mesh_handles) + }) + .collect(); + result.append(&mut inner_result); + result + } else { + Vec::new() + } + } +} + +pub(crate) trait F32Ext: Copy { + fn is_approx_zero(self) -> bool; + fn squared(self) -> f32; + fn lerp(self, other: f32, ratio: f32) -> f32; +} + +impl F32Ext for f32 { + #[inline] + fn is_approx_zero(self) -> bool { + self.abs() < 1e-5 + } + + #[inline] + fn squared(self) -> f32 { + self * self + } + + #[inline] + fn lerp(self, other: f32, ratio: f32) -> f32 { + self.mul_add(1. - ratio, other * ratio) + } +} + +pub(crate) trait TransformExt: Copy { + fn horizontally_looking_at(self, target: Vec3, up: Vec3) -> Transform; + fn lerp(self, other: Transform, ratio: f32) -> Transform; +} + +impl TransformExt for Transform { + fn horizontally_looking_at(self, target: Vec3, up: Vec3) -> Transform { + let direction = target - self.translation; + let horizontal_direction = direction - up * direction.dot(up); + let look_target = self.translation + horizontal_direction; + self.looking_at(look_target, up) + } + + fn lerp(self, other: Transform, ratio: f32) -> Transform { + let translation = self.translation.lerp(other.translation, ratio); + let rotation = self.rotation.slerp(other.rotation, ratio); + let scale = self.scale.lerp(other.scale, ratio); + Transform { + translation, + rotation, + scale, + } + } +} diff --git a/examples/bevy_gltf_save_load/basic/src/core/physics/utils_old.rs b/examples/bevy_gltf_save_load/basic/src/core/physics/utils_old.rs new file mode 100644 index 00000000..c210dd3a --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/core/physics/utils_old.rs @@ -0,0 +1,75 @@ +use bevy::prelude::*; +use bevy::render::mesh::{MeshVertexAttributeId, PrimitiveTopology, VertexAttributeValues}; +// TAKEN VERBATIB FROM https://github.com/janhohenheim/foxtrot/blob/6e31fc02652fc9d085a4adde0a73ab007dbbb0dc/src/util/trait_extension.rs + +pub trait Vec3Ext { + #[allow(clippy::wrong_self_convention)] // Because [`Vec3`] is [`Copy`] + fn is_approx_zero(self) -> bool; + fn x0z(self) -> Vec3; +} +impl Vec3Ext for Vec3 { + fn is_approx_zero(self) -> bool { + [self.x, self.y, self.z].iter().all(|&x| x.abs() < 1e-5) + } + fn x0z(self) -> Vec3 { + Vec3::new(self.x, 0., self.z) + } +} + +pub trait MeshExt { + fn transform(&mut self, transform: Transform); + fn transformed(&self, transform: Transform) -> Mesh; + fn read_coords_mut(&mut self, id: impl Into) -> &mut Vec<[f32; 3]>; + fn search_in_children<'a>( + children: &'a Children, + meshes: &'a Assets, + mesh_handles: &'a Query<&Handle>, + ) -> (Entity, &'a Mesh); +} + +impl MeshExt for Mesh { + fn transform(&mut self, transform: Transform) { + for attribute in [Mesh::ATTRIBUTE_POSITION, Mesh::ATTRIBUTE_NORMAL] { + for coords in self.read_coords_mut(attribute.clone()) { + let vec3 = (*coords).into(); + let transformed = transform.transform_point(vec3); + *coords = transformed.into(); + } + } + } + + fn transformed(&self, transform: Transform) -> Mesh { + let mut mesh = self.clone(); + mesh.transform(transform); + mesh + } + + fn read_coords_mut(&mut self, id: impl Into) -> &mut Vec<[f32; 3]> { + match self.attribute_mut(id).unwrap() { + VertexAttributeValues::Float32x3(values) => values, + // Guaranteed by Bevy + _ => unreachable!(), + } + } + + fn search_in_children<'a>( + children: &'a Children, + meshes: &'a Assets, + mesh_handles: &'a Query<&Handle>, + ) -> (Entity, &'a Mesh) { + let entity_handles: Vec<_> = children + .iter() + .filter_map(|entity| mesh_handles.get(*entity).ok().map(|mesh| (*entity, mesh))) + .collect(); + assert_eq!( + entity_handles.len(), + 1, + "Collider must contain exactly one mesh, but found {}", + entity_handles.len() + ); + let (entity, mesh_handle) = entity_handles.first().unwrap(); + let mesh = meshes.get(mesh_handle).unwrap(); + assert_eq!(mesh.primitive_topology(), PrimitiveTopology::TriangleList); + (*entity, mesh) + } +} diff --git a/examples/bevy_gltf_save_load/basic/src/core/relationships/mod.rs b/examples/bevy_gltf_save_load/basic/src/core/relationships/mod.rs new file mode 100644 index 00000000..41284536 --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/core/relationships/mod.rs @@ -0,0 +1,11 @@ +pub mod relationships_insert_dependant_components; +pub use relationships_insert_dependant_components::*; + +use bevy::prelude::*; + +pub struct EcsRelationshipsPlugin; +impl Plugin for EcsRelationshipsPlugin { + fn build(&self, app: &mut App) { + app; + } +} diff --git a/examples/bevy_gltf_save_load/basic/src/core/relationships/relationships_insert_dependant_components.rs b/examples/bevy_gltf_save_load/basic/src/core/relationships/relationships_insert_dependant_components.rs new file mode 100644 index 00000000..4e9ad172 --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/core/relationships/relationships_insert_dependant_components.rs @@ -0,0 +1,15 @@ +use bevy::prelude::*; + +pub fn insert_dependant_component< + Dependant: Component, + Dependency: Component + std::default::Default, +>( + mut commands: Commands, + entities_without_depency: Query<(Entity, &Name), (With, Without)>, +) { + for (entity, name) in entities_without_depency.iter() { + let name = name.clone().to_string(); + commands.entity(entity).insert(Dependency::default()); + warn!("found an entity called {} with a {} component but without an {}, please check your assets", name.clone(), std::any::type_name::(), std::any::type_name::()); + } +} diff --git a/examples/bevy_gltf_save_load/basic/src/game/in_game.rs b/examples/bevy_gltf_save_load/basic/src/game/in_game.rs new file mode 100644 index 00000000..d31b1a87 --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/game/in_game.rs @@ -0,0 +1,203 @@ +use super::Player; +use crate::state::{GameState, InAppRunning}; +use bevy::prelude::*; +use bevy_gltf_blueprints::{BluePrintBundle, BlueprintName, GameWorldTag, Library, NoInBlueprint}; +use bevy_gltf_save_load::{Dynamic, DynamicEntitiesRoot, StaticEntitiesRoot}; +use bevy_rapier3d::prelude::Velocity; +use rand::Rng; + +pub fn setup_game(mut commands: Commands, mut next_game_state: ResMut>) { + info!("setting up game world"); + // here we actually spawn our game world/level + let world_root = commands + .spawn(( + Name::from("world"), + GameWorldTag, + InAppRunning, + TransformBundle::default(), + InheritedVisibility::default(), + //StaticEntities("World"), + //DynamicEntities("World_dynamic") + )) + .id(); + + // and we fill it with static entities + let static_data = commands + .spawn(( + Name::from("static"), + BluePrintBundle { + blueprint: BlueprintName("World".to_string()), + ..Default::default() + }, + StaticEntitiesRoot, + Library("models".into()), + )) + .id(); + + // and we fill it with dynamic entities + let dynamic_data = commands + .spawn(( + Name::from("dynamic"), + BluePrintBundle { + blueprint: BlueprintName("World_dynamic".to_string()), + ..Default::default() + }, + DynamicEntitiesRoot, + NoInBlueprint, // we do not want the descendants of this entity to be filtered out when saving + Library("models".into()), + )) + .id(); + commands.entity(world_root).add_child(static_data); + commands.entity(world_root).add_child(dynamic_data); + + next_game_state.set(GameState::InGame) +} + +// TODO: Same as in load, reuse +pub fn unload_world(mut commands: Commands, gameworlds: Query>) { + for e in gameworlds.iter() { + info!("--loading: despawn old world/level"); + commands.entity(e).despawn_recursive(); + } +} + +pub fn should_reset(keycode: Res>) -> bool { + return keycode.just_pressed(KeyCode::N); +} + +pub fn spawn_test( + keycode: Res>, + mut dynamic_entities_world: Query>, + mut commands: Commands, +) { + if keycode.just_pressed(KeyCode::T) { + let world = dynamic_entities_world.single_mut(); + + let mut rng = rand::thread_rng(); + let range = 5.5; + let x: f32 = rng.gen_range(-range..range); + let y: f32 = rng.gen_range(-range..range); + + let mut rng = rand::thread_rng(); + let range = 0.8; + let vel_x: f32 = rng.gen_range(-range..range); + let vel_y: f32 = rng.gen_range(2.0..2.5); + let vel_z: f32 = rng.gen_range(-range..range); + + let name_index: u64 = rng.gen(); + + let new_entity = commands + .spawn(( + BluePrintBundle { + blueprint: BlueprintName("Health_Pickup".to_string()), + ..Default::default() + }, + // AddToGameWorld, // automatically added to entity (should be only one) that has the GameWorldTag + bevy::prelude::Name::from(format!("test{}", name_index)), + TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), + Velocity { + linvel: Vec3::new(vel_x, vel_y, vel_z), + angvel: Vec3::new(0.0, 0.0, 0.0), + }, + )) + .id(); + commands.entity(world).add_child(new_entity); + } +} + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +struct UnregisteredComponent; + +pub fn spawn_test_unregisted_components( + keycode: Res>, + mut commands: Commands, + + mut dynamic_entities_world: Query>, +) { + if keycode.just_pressed(KeyCode::U) { + let world = dynamic_entities_world.single_mut(); + + let mut rng = rand::thread_rng(); + let range = 5.5; + let x: f32 = rng.gen_range(-range..range); + let y: f32 = rng.gen_range(-range..range); + + let mut rng = rand::thread_rng(); + let range = 0.8; + let vel_x: f32 = rng.gen_range(-range..range); + let vel_y: f32 = rng.gen_range(2.0..2.5); + let vel_z: f32 = rng.gen_range(-range..range); + + let name_index: u64 = rng.gen(); + + let new_entity = commands + .spawn(( + BluePrintBundle { + blueprint: BlueprintName("Health_Pickup".to_string()), + ..Default::default() + }, + bevy::prelude::Name::from(format!("test{}", name_index)), + // BlueprintName("Health_Pickup".to_string()), + // SpawnHere, + TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), + Velocity { + linvel: Vec3::new(vel_x, vel_y, vel_z), + angvel: Vec3::new(0.0, 0.0, 0.0), + }, + UnregisteredComponent, + )) + .id(); + commands.entity(world).add_child(new_entity); + } +} + +pub fn spawn_test_parenting( + keycode: Res>, + players: Query>, + mut commands: Commands, + + names: Query<(Entity, &Name)>, +) { + if keycode.just_pressed(KeyCode::P) { + let mut rng = rand::thread_rng(); + let range = 5.5; + let x: f32 = rng.gen_range(-range..range); + let y: f32 = rng.gen_range(-range..range); + + let child_test = commands + .spawn(( + BluePrintBundle { + blueprint: BlueprintName("Sphero".to_string()), + ..Default::default() + }, + bevy::prelude::Name::from(format!("SubParentingTest")), + TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), + Dynamic(true), + )) + .id(); + + let parenting_test_entity = commands + .spawn(( + BluePrintBundle { + blueprint: BlueprintName("Container".into()), + ..Default::default() + }, + bevy::prelude::Name::from(format!("ParentingTest")), + Dynamic(true), + TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), + )) + .id(); + + commands.entity(parenting_test_entity).add_child(child_test); + + for player in players.iter() { + commands.entity(player).add_child(parenting_test_entity); + } + for (e, name) in names.iter() { + if name.to_string() == "Player" { + commands.entity(e).add_child(parenting_test_entity); + } + } + } +} diff --git a/examples/bevy_gltf_save_load/basic/src/game/in_game_loading.rs b/examples/bevy_gltf_save_load/basic/src/game/in_game_loading.rs new file mode 100644 index 00000000..e0d3770d --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/game/in_game_loading.rs @@ -0,0 +1,47 @@ +use bevy::{core_pipeline::clear_color::ClearColorConfig, prelude::*}; + +use crate::state::InGameLoading; + +pub fn setup_loading_screen(mut commands: Commands) { + commands.spawn(( + Camera2dBundle { + camera_2d: Camera2d { + clear_color: ClearColorConfig::Custom(Color::BLACK), + }, + camera: Camera { + // renders after / on top of the main camera + order: 2, + ..default() + }, + ..Default::default() + }, + InGameLoading, + )); + + commands.spawn(( + TextBundle::from_section( + "Loading...", + TextStyle { + font_size: 28.0, + color: Color::WHITE, + ..Default::default() + }, + ) + .with_style(Style { + position_type: PositionType::Relative, + top: Val::Percent(45.0), + left: Val::Percent(45.0), + ..default() + }), + InGameLoading, + )); +} + +pub fn teardown_loading_screen( + in_main_menu: Query>, + mut commands: Commands, +) { + for entity in in_main_menu.iter() { + commands.entity(entity).despawn_recursive(); + } +} diff --git a/examples/bevy_gltf_save_load/basic/src/game/in_game_saving.rs b/examples/bevy_gltf_save_load/basic/src/game/in_game_saving.rs new file mode 100644 index 00000000..1605ad06 --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/game/in_game_saving.rs @@ -0,0 +1,32 @@ +use bevy::prelude::*; + +use crate::state::InGameSaving; + +pub fn setup_saving_screen(mut commands: Commands) { + commands.spawn(( + TextBundle::from_section( + "Saving...", + TextStyle { + font_size: 28.0, + color: Color::WHITE, + ..Default::default() + }, + ) + .with_style(Style { + position_type: PositionType::Relative, + top: Val::Percent(90.0), + left: Val::Percent(80.0), + ..default() + }), + InGameSaving, + )); +} + +pub fn teardown_saving_screen( + in_main_menu: Query>, + mut commands: Commands, +) { + for entity in in_main_menu.iter() { + commands.entity(entity).despawn_recursive(); + } +} diff --git a/examples/bevy_gltf_save_load/basic/src/game/in_main_menu.rs b/examples/bevy_gltf_save_load/basic/src/game/in_main_menu.rs new file mode 100644 index 00000000..c8fc24a3 --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/game/in_main_menu.rs @@ -0,0 +1,116 @@ +use bevy::prelude::*; + +use crate::state::{AppState, InMainMenu}; + +pub fn setup_main_menu(mut commands: Commands) { + commands.spawn((Camera2dBundle::default(), InMainMenu)); + + commands.spawn(( + TextBundle::from_section( + "SOME GAME TITLE !!", + TextStyle { + font_size: 18.0, + color: Color::WHITE, + ..Default::default() + }, + ) + .with_style(Style { + position_type: PositionType::Absolute, + top: Val::Px(100.0), + left: Val::Px(200.0), + ..default() + }), + InMainMenu, + )); + + commands.spawn(( + TextBundle::from_section( + "New Game (press Enter to start) + - press N to restart (once the game is started) + - press S to save (once the game is started) + - press L to load (once the game is started) + - press T for demo spawning (once the game is started) + - press U to spawn entities with unregistered components (once the game is started) + - press P to spawn entities attached to the player (once the game is started) + ", + TextStyle { + font_size: 18.0, + color: Color::WHITE, + ..Default::default() + }, + ) + .with_style(Style { + position_type: PositionType::Absolute, + top: Val::Px(200.0), + left: Val::Px(200.0), + ..default() + }), + InMainMenu, + )); + + /* + commands.spawn(( + TextBundle::from_section( + "Load Game", + TextStyle { + font_size: 18.0, + color: Color::WHITE, + ..Default::default() + }, + ) + .with_style(Style { + position_type: PositionType::Absolute, + top: Val::Px(250.0), + left: Val::Px(200.0), + ..default() + }), + InMainMenu + )); + + commands.spawn(( + TextBundle::from_section( + "Exit Game", + TextStyle { + font_size: 18.0, + color: Color::WHITE, + ..Default::default() + }, + ) + .with_style(Style { + position_type: PositionType::Absolute, + top: Val::Px(300.0), + left: Val::Px(200.0), + ..default() + }), + InMainMenu + ));*/ +} + +pub fn teardown_main_menu(in_main_menu: Query>, mut commands: Commands) { + for entity in in_main_menu.iter() { + commands.entity(entity).despawn_recursive(); + } +} + +pub fn main_menu( + keycode: Res>, + + mut next_app_state: ResMut>, + // mut next_game_state: ResMut>, + // mut save_requested_events: EventWriter, + // mut load_requested_events: EventWriter, +) { + if keycode.just_pressed(KeyCode::Return) { + next_app_state.set(AppState::AppLoading); + // next_game_state.set(GameState::None); + } + + if keycode.just_pressed(KeyCode::L) { + next_app_state.set(AppState::AppLoading); + // load_requested_events.send(LoadRequest { path: "toto".into() }) + } + + if keycode.just_pressed(KeyCode::S) { + // save_requested_events.send(SaveRequest { path: "toto".into() }) + } +} diff --git a/examples/bevy_gltf_save_load/basic/src/game/mod.rs b/examples/bevy_gltf_save_load/basic/src/game/mod.rs new file mode 100644 index 00000000..8e44a20a --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/game/mod.rs @@ -0,0 +1,171 @@ +pub mod in_game; +pub use in_game::*; + +pub mod in_main_menu; +pub use in_main_menu::*; + +pub mod in_game_loading; +pub use in_game_loading::*; + +pub mod in_game_saving; +pub use in_game_saving::*; + +pub mod picking; +pub use picking::*; + +use crate::state::{AppState, GameState}; +use bevy::prelude::*; +use bevy_gltf_save_load::{LoadRequest, LoadingFinished, SaveRequest, SavingFinished}; + +// this file is just for demo purposes, contains various types of components, systems etc + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +pub enum SoundMaterial { + Metal, + Wood, + Rock, + Cloth, + Squishy, + #[default] + None, +} + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +/// Demo marker component +pub struct Player; + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +/// Demo component showing auto injection of components +pub struct ShouldBeWithPlayer; + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +/// Demo marker component +pub struct Interactible; + +fn player_move_demo( + keycode: Res>, + mut players: Query<&mut Transform, With>, +) { + let speed = 0.2; + if let Ok(mut player) = players.get_single_mut() { + if keycode.pressed(KeyCode::Left) { + player.translation.x += speed; + } + if keycode.pressed(KeyCode::Right) { + player.translation.x -= speed; + } + + if keycode.pressed(KeyCode::Up) { + player.translation.z += speed; + } + if keycode.pressed(KeyCode::Down) { + player.translation.z -= speed; + } + } +} + +pub fn request_save( + mut save_requests: EventWriter, + keycode: Res>, + + current_state: Res>, + mut next_game_state: ResMut>, +) { + if keycode.just_pressed(KeyCode::S) + && (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(), + }) + } +} + +pub fn on_saving_finished( + mut saving_finished: EventReader, + mut next_game_state: ResMut>, +) { + for _ in saving_finished.read() { + next_game_state.set(GameState::InGame); + } +} + +pub fn request_load( + mut load_requests: EventWriter, + keycode: Res>, + current_state: Res>, + mut next_game_state: ResMut>, +) { + if keycode.just_pressed(KeyCode::L) + && (current_state.get() != &GameState::InLoading) + && (current_state.get() != &GameState::InSaving) + { + next_game_state.set(GameState::InLoading); + load_requests.send(LoadRequest { + path: "save.scn.ron".into(), + }) + } +} + +pub fn on_loading_finished( + mut loading_finished: EventReader, + mut next_game_state: ResMut>, +) { + for _ in loading_finished.read() { + next_game_state.set(GameState::InGame); + } +} + +pub struct GamePlugin; +impl Plugin for GamePlugin { + fn build(&self, app: &mut App) { + app.add_plugins(PickingPlugin) + .register_type::() + .register_type::() + .register_type::() + .add_systems( + Update, + ( + // little helper utility, to automatically inject components that are dependant on an other component + // ie, here an Entity with a Player component should also always have a ShouldBeWithPlayer component + // you get a warning if you use this, as I consider this to be stop-gap solution (usually you should have either a bundle, or directly define all needed components) + + // insert_dependant_component::, + player_move_demo, //.run_if(in_state(AppState::Running)), + spawn_test, + spawn_test_unregisted_components, + spawn_test_parenting, + ) + .run_if(in_state(GameState::InGame)), + ) + .add_systems( + Update, + (unload_world, apply_deferred, setup_game) + .chain() + .run_if(should_reset) + .run_if(in_state(AppState::AppRunning)), + ) + .add_systems( + Update, + ( + request_save, + request_load, + on_saving_finished, + on_loading_finished, + ), + ) + .add_systems(OnEnter(AppState::MenuRunning), setup_main_menu) + .add_systems(OnExit(AppState::MenuRunning), teardown_main_menu) + .add_systems(Update, main_menu.run_if(in_state(AppState::MenuRunning))) + .add_systems(OnEnter(GameState::InLoading), setup_loading_screen) + .add_systems(OnExit(GameState::InLoading), teardown_loading_screen) + .add_systems(OnEnter(GameState::InSaving), setup_saving_screen) + .add_systems(OnExit(GameState::InSaving), teardown_saving_screen) + .add_systems(OnEnter(AppState::AppRunning), setup_game); + } +} diff --git a/examples/bevy_gltf_save_load/basic/src/game/picking.rs b/examples/bevy_gltf_save_load/basic/src/game/picking.rs new file mode 100644 index 00000000..6731bbb5 --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/game/picking.rs @@ -0,0 +1,34 @@ +use super::Player; +use bevy::prelude::*; +use bevy_gltf_blueprints::GltfBlueprintsSet; + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +pub struct Pickable; + +// very simple, crude picking (as in picking up objects) implementation + +pub fn picking( + players: Query<&GlobalTransform, With>, + pickables: Query<(Entity, &GlobalTransform), With>, + mut commands: Commands, +) { + for player_transforms in players.iter() { + for (pickable, pickable_transforms) in pickables.iter() { + let distance = player_transforms + .translation() + .distance(pickable_transforms.translation()); + if distance < 2.5 { + commands.entity(pickable).despawn_recursive(); + } + } + } +} + +pub struct PickingPlugin; +impl Plugin for PickingPlugin { + fn build(&self, app: &mut App) { + app.register_type::() + .add_systems(Update, (picking.after(GltfBlueprintsSet::AfterSpawn),)); + } +} diff --git a/examples/bevy_gltf_save_load/basic/src/main.rs b/examples/bevy_gltf_save_load/basic/src/main.rs new file mode 100644 index 00000000..6edba337 --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/main.rs @@ -0,0 +1,33 @@ +use bevy::prelude::*; +use bevy_editor_pls::prelude::*; + +mod core; +use crate::core::*; + +pub mod assets; +use assets::*; + +pub mod state; +use state::*; + +mod game; +use game::*; + +mod test_components; +use test_components::*; + +fn main() { + App::new() + .add_plugins(( + DefaultPlugins.set(AssetPlugin::default()), + // editor + EditorPlugin::default(), + // our custom plugins + StatePlugin, + AssetsPlugin, + CorePlugin, // reusable plugins + GamePlugin, // specific to our game + ComponentsTestPlugin, // Showcases different type of components /structs + )) + .run(); +} diff --git a/examples/bevy_gltf_save_load/basic/src/state.rs b/examples/bevy_gltf_save_load/basic/src/state.rs new file mode 100644 index 00000000..725ac7f2 --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/state.rs @@ -0,0 +1,57 @@ +use bevy::prelude::*; + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default, States)] +pub enum AppState { + #[default] + CoreLoading, + MenuRunning, + AppLoading, + AppRunning, + AppEnding, +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default, States)] +pub enum GameState { + #[default] + None, + + InMenu, + InGame, + + InGameOver, + + InSaving, + InLoading, +} + +// tag components for all entities within a certain state (for despawning them if needed) , FIXME: seems kinda hack-ish +#[derive(Component)] +pub struct InCoreLoading; +#[derive(Component, Default)] +pub struct InMenuRunning; +#[derive(Component)] + +pub struct InAppRunning; + +// components for tagging in game vs in game menu stuff +#[derive(Component, Default)] +pub struct InMainMenu; + +#[derive(Component, Default)] +pub struct InMenu; + +#[derive(Component, Default)] +pub struct InGame; + +#[derive(Component, Default)] +pub struct InGameSaving; + +#[derive(Component, Default)] +pub struct InGameLoading; + +pub struct StatePlugin; +impl Plugin for StatePlugin { + fn build(&self, app: &mut App) { + app.add_state::().add_state::(); + } +} diff --git a/examples/bevy_gltf_save_load/basic/src/test_components.rs b/examples/bevy_gltf_save_load/basic/src/test_components.rs new file mode 100644 index 00000000..d0e6fbd7 --- /dev/null +++ b/examples/bevy_gltf_save_load/basic/src/test_components.rs @@ -0,0 +1,80 @@ +use bevy::prelude::*; + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +struct UnitTest; + +#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)] +#[reflect(Component)] +struct TuppleTestF32(f32); + +#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)] +#[reflect(Component)] +struct TuppleTestU64(u64); + +#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)] +#[reflect(Component)] +pub struct TuppleTestStr(String); + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +struct TuppleTest2(f32, u64, String); + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +struct TuppleTestBool(bool); + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +struct TuppleVec2(Vec2); + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +struct TuppleVec3(Vec3); + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +struct TuppleVec(Vec); + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +struct TuppleTestColor(Color); + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +struct BasicTest { + a: f32, + b: u64, + c: String, +} + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +pub enum EnumTest { + Metal, + Wood, + Rock, + Cloth, + Squishy, + #[default] + None, +} + +pub struct ComponentsTestPlugin; +impl Plugin for ComponentsTestPlugin { + fn build(&self, app: &mut App) { + app.register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::>(); + } +} diff --git a/tools/gltf_auto_export/README.md b/tools/gltf_auto_export/README.md index 7a53fd9c..fb577bd7 100644 --- a/tools/gltf_auto_export/README.md +++ b/tools/gltf_auto_export/README.md @@ -102,6 +102,17 @@ This issue has been resolved in v0.9. please read the dedicated [section](#collection-instances--nested-blueprints) below for more information + + - Export dynamic and static objects seperatly : For MAIN scenes only (aka levels), toggle this to generate 2 files per level: + + - one with all dynamic data: collection or instances marked as dynamic (aka saveable) + - one with all static data: anything else that is NOT marked as dynamic, the file name will have the suffix **_dynamic** + + Ie if you add a "Dynamic" custom property/ component to either your collection instances or your blueprint, you get a clean seperation between + + - your static level data (anything that will never change during the lifetime of your Bevy app) + - your dynamic objects (anything that will change during the lifetime of your Bevy app, that can be saved & reloaded in save files for example) + - export materials library: check this if you want to automatically export material libraries (default: False) please read the dedicated [section](#materials) below for more information @@ -233,7 +244,7 @@ There are only a few things to keep in mind Take a look at the [relevant](../../examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/) example for more [details](../../examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/art/) -### Process +### Internal Process overview This is the internal logic of the export process with blueprints (simplified) diff --git a/tools/gltf_auto_export/__init__.py b/tools/gltf_auto_export/__init__.py index 716e1848..a9eceebb 100644 --- a/tools/gltf_auto_export/__init__.py +++ b/tools/gltf_auto_export/__init__.py @@ -1,7 +1,7 @@ bl_info = { "name": "gltf_auto_export", "author": "kaosigh", - "version": (0, 9, 0), + "version": (0, 10, 0), "blender": (3, 4, 0), "location": "File > Import-Export", "description": "glTF/glb auto-export", diff --git a/tools/gltf_auto_export/docs/blender_addon_use3.png b/tools/gltf_auto_export/docs/blender_addon_use3.png index f16899bf..322cd69f 100644 Binary files a/tools/gltf_auto_export/docs/blender_addon_use3.png and b/tools/gltf_auto_export/docs/blender_addon_use3.png differ diff --git a/tools/gltf_auto_export/dynamic.py b/tools/gltf_auto_export/dynamic.py new file mode 100644 index 00000000..62addc16 --- /dev/null +++ b/tools/gltf_auto_export/dynamic.py @@ -0,0 +1,32 @@ +import bpy + + +# 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: we need to recompute these on blueprint changes too +# even better, keep a list of dynamic objects per scene , updated only when needed ? +def is_object_dynamic(object): + 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 + if not is_dynamic and object.type == 'EMPTY' and hasattr(object, 'instance_collection'): + # get the name of the collection this is an instance of + collection_name = object.instance_collection.name + original_collection = bpy.data.collections[collection_name] + + #print("inspecting insides", original_collection) + + # scan original collection, look for a 'Dynamic' flag + for object in original_collection.objects: + #print(" inner", object) + if object.type == 'EMPTY' and object.name.endswith("components"): + for component_name in object.keys(): + #print(" compo", component_name) + if component_name == 'Dynamic': + is_dynamic = True + break + print("is object dynamic", is_dynamic, object) + + return is_dynamic + +def is_object_static(object): + return not is_object_dynamic(object) \ No newline at end of file diff --git a/tools/gltf_auto_export/helpers_export.py b/tools/gltf_auto_export/helpers_export.py index 346adb94..9acfb7d2 100644 --- a/tools/gltf_auto_export/helpers_export.py +++ b/tools/gltf_auto_export/helpers_export.py @@ -1,11 +1,13 @@ import os import bpy + from .preferences import (AutoExportGltfPreferenceNames) from .helpers_scenes import (generate_hollow_scene, clear_hollow_scene) from .helpers_collections import (recurLayerCollection) from .blueprints import clear_blueprint_hollow_scene, generate_blueprint_hollow_scene from .helpers import (traverse_tree) +from .dynamic import (is_object_dynamic, is_object_static) ###################################################### #### Export logic ##### @@ -138,19 +140,9 @@ def export_main_scene(scene, folder_path, addon_prefs, library_collections): gltf_export_preferences = generate_gltf_export_preferences(addon_prefs) export_output_folder = getattr(addon_prefs,"export_output_folder") export_blueprints = getattr(addon_prefs,"export_blueprints") - - if export_blueprints : - (hollow_scene, temporary_collections, root_objects, special_properties) = generate_hollow_scene(scene, library_collections, addon_prefs) - #except Exception: - # print("failed to create hollow scene") - - # set active scene to be the given scene - bpy.context.window.scene = hollow_scene - + export_separate_dynamic_and_static_objects = getattr(addon_prefs, "export_separate_dynamic_and_static_objects") + gltf_output_path = os.path.join(folder_path, export_output_folder, scene.name) - print(" exporting gltf to", gltf_output_path, ".gltf/glb") - - export_settings = { **gltf_export_preferences, 'use_active_scene': True, 'use_active_collection':True, @@ -159,10 +151,40 @@ def export_main_scene(scene, folder_path, addon_prefs, library_collections): 'use_renderable': False, 'export_apply':True } - export_gltf(gltf_output_path, export_settings) if export_blueprints : - clear_hollow_scene(hollow_scene, scene, temporary_collections, root_objects, special_properties) + if export_separate_dynamic_and_static_objects: + # first export all dynamic objects + (hollow_scene, temporary_collections, root_objects, special_properties) = generate_hollow_scene(scene, library_collections, addon_prefs, is_object_dynamic) + gltf_output_path = os.path.join(folder_path, export_output_folder, scene.name+ "_dynamic") + # set active scene to be the given scene + bpy.context.window.scene = hollow_scene + print(" exporting gltf to", gltf_output_path, ".gltf/glb") + export_gltf(gltf_output_path, export_settings) + clear_hollow_scene(hollow_scene, scene, temporary_collections, root_objects, special_properties) + + # now export static objects + (hollow_scene, temporary_collections, root_objects, special_properties) = generate_hollow_scene(scene, library_collections, addon_prefs, is_object_static) + gltf_output_path = os.path.join(folder_path, export_output_folder, scene.name) + # set active scene to be the given scene + bpy.context.window.scene = hollow_scene + print(" exporting gltf to", gltf_output_path, ".gltf/glb") + export_gltf(gltf_output_path, export_settings) + clear_hollow_scene(hollow_scene, scene, temporary_collections, root_objects, special_properties) + + else: + print("NO SPLIT") + # todo: add exception handling + (hollow_scene, temporary_collections, root_objects, special_properties) = generate_hollow_scene(scene, library_collections, addon_prefs) + # set active scene to be the given scene + bpy.context.window.scene = hollow_scene + print(" exporting gltf to", gltf_output_path, ".gltf/glb") + export_gltf(gltf_output_path, export_settings) + + clear_hollow_scene(hollow_scene, scene, temporary_collections, root_objects, special_properties) + else: + print(" exporting gltf to", gltf_output_path, ".gltf/glb") + export_gltf(gltf_output_path, export_settings) #https://docs.blender.org/api/current/bpy.ops.export_scene.html#bpy.ops.export_scene.gltf diff --git a/tools/gltf_auto_export/helpers_scenes.py b/tools/gltf_auto_export/helpers_scenes.py index 98a6b0af..d0a8bbdd 100644 --- a/tools/gltf_auto_export/helpers_scenes.py +++ b/tools/gltf_auto_export/helpers_scenes.py @@ -4,7 +4,7 @@ # generate a copy of a scene that replaces collection instances with empties # copy original names before creating a new scene, & reset them -def generate_hollow_scene(scene, library_collections, addon_prefs): +def generate_hollow_scene(scene, library_collections, addon_prefs, filter=None): collection_instances_combine_mode = getattr(addon_prefs, "collection_instances_combine_mode") root_collection = scene.collection @@ -29,6 +29,8 @@ def generate_hollow_scene(scene, library_collections, addon_prefs): # copies the contents of a collection into another one while replacing library instances with empties def copy_hollowed_collection_into(source_collection, destination_collection, parent_empty=None): for object in source_collection.objects: + if filter is not None and filter(object) is False: + continue #check if a specific collection instance does not have an ovveride for combine_mode combine_mode = object['_combine'] if '_combine' in object else collection_instances_combine_mode @@ -75,17 +77,6 @@ def copy_hollowed_collection_into(source_collection, destination_collection, par copy_hollowed_collection_into(collection, destination_collection, collection_placeholder) - - """ - copy_collection = bpy.data.collections.new(collection.name + "____collection_export") - # save the newly created collection for later reuse - temporary_collections.append(copy_collection) - - # copy & link objects - copy_hollowed_collection_into(collection, copy_collection) - destination_collection.children.link(copy_collection) - """ - copy_hollowed_collection_into(root_collection, copy_root_collection) return (temp_scene, temporary_collections, root_objects, special_properties) diff --git a/tools/gltf_auto_export/preferences.py b/tools/gltf_auto_export/preferences.py index 2c11c469..b86e5d45 100644 --- a/tools/gltf_auto_export/preferences.py +++ b/tools/gltf_auto_export/preferences.py @@ -21,6 +21,7 @@ 'export_marked_assets', 'collection_instances_combine_mode', + 'export_separate_dynamic_and_static_objects', 'export_materials_library', 'export_materials_path', @@ -89,7 +90,6 @@ class AutoExportGltfAddonPreferences(AddonPreferences): default='materials' ) - """ combine mode can be - 'Split' (default): replace with an empty, creating links to sub blueprints - 'Embed' : treat it as an embeded object and do not replace it with an empty @@ -115,6 +115,15 @@ class AutoExportGltfAddonPreferences(AddonPreferences): default=True ) + export_separate_dynamic_and_static_objects: BoolProperty( + name='Export dynamic and static objects seperatly', + description="""For MAIN scenes only (aka levels), toggle this to generate 2 files per level: + - one with all dynamic data: collection or instances marked as dynamic/ saveable + - one with all static data: anything else that is NOT marked as dynamic""", + default=True + ) + + main_scenes: CollectionProperty(name="main scenes", type=CUSTOM_PG_sceneName) main_scenes_index: IntProperty(name = "Index for main scenes list", default = 0) diff --git a/tools/gltf_auto_export/ui/main.py b/tools/gltf_auto_export/ui/main.py index a4f2368b..78be74c4 100644 --- a/tools/gltf_auto_export/ui/main.py +++ b/tools/gltf_auto_export/ui/main.py @@ -302,6 +302,7 @@ def draw(self, context): layout.prop(operator, "export_blueprints_path") layout.prop(operator, "collection_instances_combine_mode") layout.prop(operator, "export_marked_assets") + layout.prop(operator, "export_separate_dynamic_and_static_objects") layout.separator() # materials layout.prop(operator, "export_materials_library")