mirror of
https://github.com/kaosat-dev/Blender_bevy_components_workflow.git
synced 2024-11-24 12:50:53 +00:00
feat(Save & load): new crate bevy_gltf_save_load + lots of upgrades & improvements (#95)
* feat(bevy_gltf_save_load): saving & loading implemented * created new crate for save & load features, uses & filters out blueprints for efficient loading * saving & loading, even with dynamically spawned nested hierarchies works * component filter , resource filter & save path root are configurable * for saving: added removal & cleanup logic for children component with children that have been filtered out: ie no more invalid children getting stored in the save files ! * added sending of event once saving is done * feat(examples/save-load): example for the new crate * loading level static & dynamic data as blueprints * added a bit of ui when entering saving & loading states & cleanup when exiting * feat(bevy_gltf_blueprints): significant rewrite of how the crate works * simplified spawning process, no more spawning children containing blueprints etc * simplified post process : instead of copying original entity into blueprint root we now copy blueprint root data (components & children) into the original entity ! fixes #96 * much simpler code wise * solves issue with needing to register components that we only use on the bevy side (not gltf) since we are not copying the bevy data into the blueprints data * added **copyComponents** helper to copy components from one entity to another, excluding existing components on the target entity, & some bells & whistles * **Name** is now optional when spawning a blueprint: closes #97 * **Transform** is now optional when spawning a blueprint: closes #98 * removed transform from bundle (BREAKING change) * added (optional) **NoInBlueprint** component to have finer control over whether to inject the **InBlueprint** component inside spawned blueprint entities * added (optional) **Library** component, so we can override library path when we want * added (optional) **AddToGameWorld** component for convenience to automatically add entities to the game world, if there is one * chore(bevy_gltf_components): removed verbose output, cleaned it up a bit * feat(tools/auto_export): added option to split out "dynamic" objects in main scenes * ie if a collection instance (or its original collection) has a "dynamic" (aka mutable, saveable etc) flag it can get exported to a seperate gltf file (essentially acting like an "initial save") * the rest of the levels (the "static" data) is exported without the dynamic objects and can be reused with save files !
This commit is contained in:
parent
42c1c71b03
commit
5429bf4779
77
Cargo.lock
generated
77
Cargo.lock
generated
@ -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,20 +898,11 @@ dependencies = [
|
||||
"bevy",
|
||||
"bevy_asset_loader",
|
||||
"bevy_editor_pls",
|
||||
"bevy_gltf_blueprints",
|
||||
"bevy_gltf_blueprints 0.6.0",
|
||||
"bevy_rapier3d",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_gltf_components"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"bevy",
|
||||
"ron",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_gltf_components"
|
||||
version = "0.2.0"
|
||||
@ -913,13 +914,22 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_gltf_components"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"bevy",
|
||||
"ron",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_gltf_components_basic_example"
|
||||
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"
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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` |
|
||||
|
@ -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<SpawnedRoot>, Without<Aabb>),
|
||||
>,
|
||||
root_entities: Query<(Entity, &Name), (With<Spawned>, Without<Aabb>)>,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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::<AppTypeRegistry>()
|
||||
.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::<ReflectComponent>().unwrap().clone();
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
109
crates/bevy_gltf_blueprints/src/copy_components.rs
Normal file
109
crates/bevy_gltf_blueprints/src/copy_components.rs
Normal file
@ -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<TypeId>,
|
||||
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::<AppTypeRegistry>()
|
||||
.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::<ReflectComponent>().unwrap().clone(),
|
||||
type_id.type_info().type_id().clone(), // we need the original type_id down the line
|
||||
);
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
@ -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),
|
||||
);
|
||||
|
@ -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<Entity>);
|
||||
|
||||
/// 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<BlueprintName>,
|
||||
Added<SpawnHere>,
|
||||
Without<Spawned>,
|
||||
Without<SpawnedRoot>,
|
||||
Entity,
|
||||
&BlueprintName,
|
||||
Option<&Transform>,
|
||||
Option<&Parent>,
|
||||
Option<&Library>,
|
||||
Option<&AddToGameWorld>,
|
||||
Option<&Name>,
|
||||
),
|
||||
(Added<BlueprintName>, Added<SpawnHere>, Without<Spawned>),
|
||||
>,
|
||||
|
||||
mut commands: Commands,
|
||||
mut game_world: Query<(Entity, &Children), With<GameWorldTag>>,
|
||||
mut game_world: Query<Entity, With<GameWorldTag>>,
|
||||
|
||||
assets_gltf: Res<Assets<Gltf>>,
|
||||
asset_server: Res<AssetServer>,
|
||||
blueprints_config: Res<BluePrintsConfig>,
|
||||
|
||||
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<Entity> = 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<Gltf> = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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};
|
||||
use super::{SpawnHere, Spawned};
|
||||
use crate::{CopyComponents, InBlueprint, NoInBlueprint, OriginalChildren};
|
||||
|
||||
#[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(
|
||||
//
|
||||
/// 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<SpawnedRoot>, Without<SpawnedRootProcessed>),
|
||||
(
|
||||
Entity,
|
||||
&Children,
|
||||
&OriginalChildren,
|
||||
&Animations,
|
||||
Option<&NoInBlueprint>,
|
||||
Option<&Name>,
|
||||
),
|
||||
(With<SpawnHere>, With<SceneInstance>, With<Spawned>),
|
||||
>,
|
||||
mut commands: Commands,
|
||||
|
||||
animations: Query<&Animations>,
|
||||
added_animation_players: Query<(Entity, &Parent), Added<AnimationPlayer>>,
|
||||
all_children: Query<&Children>,
|
||||
|
||||
mut commands: Commands,
|
||||
) {
|
||||
/*
|
||||
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
|
||||
for (original, children, original_children, animations, no_inblueprint, name) in
|
||||
unprocessed_entities.iter()
|
||||
{
|
||||
debug!("post processing blueprint for entity {:?}", name);
|
||||
|
||||
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 ?
|
||||
|
||||
FIME: this is all highly dependent on the hierachy ;..
|
||||
*/
|
||||
|
||||
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();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// add missing name of entity, based on the wrapper's name
|
||||
let name = name.clone();
|
||||
// copy components into from blueprint instance's root_entity to original entity
|
||||
commands.add(CopyComponents {
|
||||
source: root_entity,
|
||||
destination: original,
|
||||
exclude: vec![TypeId::of::<Parent>(), TypeId::of::<Children>()],
|
||||
stringent: false,
|
||||
});
|
||||
|
||||
// this is our new actual entity
|
||||
commands.entity(*root_entity).insert((
|
||||
bevy::prelude::Name::from(name.clone()),
|
||||
// ItemType {name},
|
||||
));
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
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(),
|
||||
});
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
commands.add(CloneEntity {
|
||||
source: original.0,
|
||||
destination: *root_entity,
|
||||
});
|
||||
|
||||
// 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::<SpawnHere>();
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<SpawnedRootProcessed>>,
|
||||
without_children: Query<Entity, (With<SpawnedRootProcessed>, Without<Children>)>, // 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();
|
||||
}
|
||||
}
|
||||
for entity in without_children.iter() {
|
||||
debug!("cleaning up emptied spawned scene instance");
|
||||
commands.entity(entity).despawn_recursive();
|
||||
commands.entity(original).remove::<SpawnHere>();
|
||||
commands.entity(original).remove::<Spawned>();
|
||||
commands.entity(original).remove::<Handle<Scene>>();
|
||||
commands.entity(root_entity).despawn_recursive();
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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");
|
||||
}
|
||||
|
18
crates/bevy_gltf_save_load/Cargo.toml
Normal file
18
crates/bevy_gltf_save_load/Cargo.toml
Normal file
@ -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"
|
4
crates/bevy_gltf_save_load/LICENSE.md
Normal file
4
crates/bevy_gltf_save_load/LICENSE.md
Normal file
@ -0,0 +1,4 @@
|
||||
This crate is available under either:
|
||||
|
||||
* The [MIT License](./LICENSE_MIT)
|
||||
* The [Apache License, Version 2.0](./LICENSE_APACHE)
|
201
crates/bevy_gltf_save_load/LICENSE_APACHE.md
Normal file
201
crates/bevy_gltf_save_load/LICENSE_APACHE.md
Normal file
@ -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.
|
21
crates/bevy_gltf_save_load/LICENSE_MIT.md
Normal file
21
crates/bevy_gltf_save_load/LICENSE_MIT.md
Normal file
@ -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.
|
312
crates/bevy_gltf_save_load/README.md
Normal file
312
crates/bevy_gltf_save_load/README.md
Normal file
@ -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<SaveRequest>,
|
||||
keycode: Res<Input<KeyCode>>,
|
||||
)
|
||||
{
|
||||
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<LoadRequest>,
|
||||
keycode: Res<Input<KeyCode>>,
|
||||
)
|
||||
{
|
||||
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<NextState<GameState>>,
|
||||
) {
|
||||
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::<Name>(),
|
||||
TypeId::of::<Transform>(),
|
||||
TypeId::of::<Velocity>(),
|
||||
// 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<SaveRequest>,
|
||||
keycode: Res<Input<KeyCode>>,
|
||||
)
|
||||
{
|
||||
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<LoadRequest>,
|
||||
keycode: Res<Input<KeyCode>>,
|
||||
)
|
||||
{
|
||||
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)
|
216
crates/bevy_gltf_save_load/src/gltf_out_test.rs
Normal file
216
crates/bevy_gltf_save_load/src/gltf_out_test.rs
Normal file
@ -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<S>(serialize: S) -> Result<String, json::Error>
|
||||
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::<Events<SaveRequest>>();
|
||||
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<Entity> = world
|
||||
.query_filtered::<Entity, With<Dynamic>>()
|
||||
.iter(world)
|
||||
.collect();
|
||||
|
||||
debug!("saveable entities {}", saveable_entities.len());
|
||||
|
||||
let components = HashSet::from([
|
||||
TypeId::of::<Name>(),
|
||||
TypeId::of::<Transform>(),
|
||||
TypeId::of::<Velocity>() ,
|
||||
TypeId::of::<BlueprintName>(),
|
||||
TypeId::of::<SpawnHere>(),
|
||||
TypeId::of::<Dynamic>(),
|
||||
|
||||
|
||||
|
||||
TypeId::of::<Camera>(),
|
||||
TypeId::of::<Camera3d>(),
|
||||
TypeId::of::<Tonemapping>(),
|
||||
TypeId::of::<CameraTrackingOffset>(),
|
||||
TypeId::of::<Projection>(),
|
||||
TypeId::of::<CameraRenderGraph>(),
|
||||
TypeId::of::<Frustum>(),
|
||||
TypeId::of::<GlobalTransform>(),
|
||||
TypeId::of::<VisibleEntities>(),
|
||||
|
||||
TypeId::of::<Pickable>(),
|
||||
|
||||
|
||||
|
||||
]);
|
||||
|
||||
let filter = SceneFilter::Allowlist(components);
|
||||
|
||||
|
||||
let mut scene_builder = DynamicSceneBuilder::from_world(world).with_filter(filter);
|
||||
|
||||
let dyn_scene = scene_builder
|
||||
|
||||
|
||||
/* .allow::<Transform>()
|
||||
.allow::<Velocity>()
|
||||
.allow::<BlueprintName>()*/
|
||||
|
||||
/* .deny::<Children>()
|
||||
.deny::<Parent>()
|
||||
.deny::<InheritedVisibility>()
|
||||
.deny::<Visibility>()
|
||||
.deny::<GltfExtras>()
|
||||
.deny::<GlobalTransform>()
|
||||
.deny::<Collider>()
|
||||
.deny::<RigidBody>()
|
||||
.deny::<Saveable>()
|
||||
// camera stuff
|
||||
.deny::<Camera>()
|
||||
.deny::<CameraRenderGraph>()
|
||||
.deny::<Camera3d>()
|
||||
.deny::<Clusters>()
|
||||
.deny::<VisibleEntities>()
|
||||
.deny::<VisiblePointLights>()
|
||||
//.deny::<HasGizmoMarker>()
|
||||
*/
|
||||
.extract_entities(saveable_entities.into_iter())
|
||||
.build();
|
||||
|
||||
let serialized_scene = dyn_scene
|
||||
.serialize_ron(world.resource::<AppTypeRegistry>())
|
||||
.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<Type::Perspective>,
|
||||
};*/
|
||||
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();
|
||||
}
|
108
crates/bevy_gltf_save_load/src/lib.rs
Normal file
108
crates/bevy_gltf_save_load/src/lib.rs
Normal file
@ -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::<Dynamic>()
|
||||
.register_type::<StaticEntitiesRoot>()
|
||||
// TODO: remove these in bevy 0.13, as these are now registered by default
|
||||
.register_type::<Camera3dDepthTextureUsage>()
|
||||
.register_type::<ScreenSpaceTransmissionQuality>()
|
||||
.register_type::<StaticEntitiesStorage>()
|
||||
.add_event::<SaveRequest>()
|
||||
.add_event::<LoadRequest>()
|
||||
.add_event::<LoadingFinished>()
|
||||
.add_event::<SavingFinished>()
|
||||
.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::<LoadRequested>())
|
||||
.run_if(not(resource_exists::<LoadFirstStageDone>()))
|
||||
.in_set(LoadingSet::Load),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
(load_static, apply_deferred, cleanup_loaded_scene)
|
||||
.chain()
|
||||
.run_if(resource_exists::<LoadFirstStageDone>())
|
||||
// .run_if(in_state(AppState::LoadingGame))
|
||||
.in_set(LoadingSet::Load),
|
||||
);
|
||||
}
|
||||
}
|
156
crates/bevy_gltf_save_load/src/loading.rs
Normal file
156
crates/bevy_gltf_save_load/src/loading.rs
Normal file
@ -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<LoadRequest>,
|
||||
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<Entity, With<GameWorldTag>>) {
|
||||
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<AssetServer>,
|
||||
load_request: Res<LoadRequested>,
|
||||
save_load_config: Res<SaveLoadConfig>,
|
||||
) {
|
||||
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<Entity, With<SceneInstance>>,
|
||||
world_root: Query<Entity, With<GameWorldTag>>,
|
||||
mut commands: Commands,
|
||||
mut loading_finished: EventWriter<LoadingFinished>,
|
||||
|
||||
static_entities: Option<Res<StaticEntitiesStorage>>,
|
||||
) {
|
||||
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<CleanupScene>,
|
||||
With<SceneInstance>,
|
||||
With<DynamicEntitiesRoot>,
|
||||
),
|
||||
>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
for loaded_scene in loaded_scenes.iter() {
|
||||
info!("REMOVING DynamicScene");
|
||||
commands
|
||||
.entity(loaded_scene)
|
||||
.remove::<Handle<DynamicScene>>()
|
||||
.remove::<SceneInstance>()
|
||||
.remove::<CleanupScene>();
|
||||
|
||||
commands.remove_resource::<LoadRequested>();
|
||||
commands.remove_resource::<LoadFirstStageDone>();
|
||||
}
|
||||
}
|
6
crates/bevy_gltf_save_load/src/saveable.rs
Normal file
6
crates/bevy_gltf_save_load/src/saveable.rs
Normal file
@ -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);
|
192
crates/bevy_gltf_save_load/src/saving.rs
Normal file
192
crates/bevy_gltf_save_load/src/saving.rs
Normal file
@ -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<SaveRequest>) -> 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<Entity, (With<Dynamic>, With<BlueprintName>)>,
|
||||
root_entities: Query<Entity, Or<(With<DynamicEntitiesRoot>, Without<Parent>)>>, // With<DynamicEntitiesRoot>
|
||||
dynamic_entities: Query<(Entity, &Parent, Option<&Children>), With<Dynamic>>,
|
||||
static_entities: Query<(Entity, &BlueprintName, Option<&Library>), With<StaticEntitiesRoot>>,
|
||||
|
||||
mut commands: Commands,
|
||||
) {
|
||||
for entity in saveables.iter() {
|
||||
commands.entity(entity).insert(SpawnHere);
|
||||
}
|
||||
|
||||
for (entity, parent, children) in dynamic_entities.iter() {
|
||||
let parent = parent.get();
|
||||
if root_entities.contains(parent) {
|
||||
commands.entity(entity).insert(RootEntity);
|
||||
}
|
||||
|
||||
if let Some(children) = children {
|
||||
for sub_child in children.iter() {
|
||||
if !dynamic_entities.contains(*sub_child) {
|
||||
commands.entity(*sub_child).insert(OriginalParent(entity));
|
||||
commands.entity(entity).remove_children(&[*sub_child]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (_, blueprint_name, library) in static_entities.iter() {
|
||||
let library_path: String = library
|
||||
.map_or_else(|| "", |l| &l.0.to_str().unwrap())
|
||||
.into();
|
||||
commands.insert_resource(StaticEntitiesStorage {
|
||||
name: blueprint_name.0.clone(),
|
||||
library_path,
|
||||
});
|
||||
}
|
||||
}
|
||||
pub(crate) fn save_game(world: &mut World) {
|
||||
info!("saving");
|
||||
|
||||
let mut save_path: String = "".into();
|
||||
let mut events = world.resource_mut::<Events<SaveRequest>>();
|
||||
|
||||
for event in events.get_reader().read(&events) {
|
||||
info!("SAVE EVENT !! {:?}", event);
|
||||
save_path = event.path.clone();
|
||||
}
|
||||
events.clear();
|
||||
|
||||
let saveable_entities: Vec<Entity> = world
|
||||
.query_filtered::<Entity, (With<Dynamic>, Without<InBlueprint>, Without<RootEntity>)>()
|
||||
.iter(world)
|
||||
.collect();
|
||||
|
||||
let saveable_root_entities: Vec<Entity> = world
|
||||
.query_filtered::<Entity, (With<Dynamic>, Without<InBlueprint>, With<RootEntity>)>()
|
||||
.iter(world)
|
||||
.collect();
|
||||
|
||||
info!("saveable entities {}", saveable_entities.len());
|
||||
info!("saveable root entities {}", saveable_root_entities.len());
|
||||
|
||||
let save_load_config = world
|
||||
.get_resource::<SaveLoadConfig>()
|
||||
.expect("SaveLoadConfig should exist at this stage");
|
||||
|
||||
// we hardcode some of the always allowed types
|
||||
let filter = save_load_config
|
||||
.component_filter
|
||||
.clone()
|
||||
.allow::<Parent>()
|
||||
.allow::<Children>()
|
||||
.allow::<BlueprintName>()
|
||||
.allow::<SpawnHere>()
|
||||
.allow::<Dynamic>();
|
||||
|
||||
// for root entities, it is the same EXCEPT we make sure parents are not included
|
||||
let filter_root = filter.clone().deny::<Parent>();
|
||||
|
||||
let filter_resources = save_load_config
|
||||
.resource_filter
|
||||
.clone()
|
||||
.allow::<StaticEntitiesStorage>();
|
||||
|
||||
// for default stuff
|
||||
let scene_builder = DynamicSceneBuilder::from_world(world)
|
||||
.with_filter(filter.clone())
|
||||
.with_resource_filter(filter_resources.clone());
|
||||
|
||||
let mut dyn_scene = scene_builder
|
||||
.extract_resources()
|
||||
.extract_entities(saveable_entities.clone().into_iter())
|
||||
.remove_empty_entities()
|
||||
.build();
|
||||
|
||||
// for root entities
|
||||
let scene_builder_root = DynamicSceneBuilder::from_world(world)
|
||||
.with_filter(filter_root.clone())
|
||||
.with_resource_filter(filter_resources.clone());
|
||||
|
||||
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::<AppTypeRegistry>())
|
||||
.unwrap();
|
||||
|
||||
let save_path = Path::new("assets")
|
||||
.join(&save_load_config.save_path)
|
||||
.join(Path::new(save_path.as_str())); // Path::new(&save_load_config.save_path).join(Path::new(save_path.as_str()));
|
||||
info!("saving game to {:?}", save_path);
|
||||
|
||||
// world.send_event(SavingFinished);
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
IoTaskPool::get()
|
||||
.spawn(async move {
|
||||
// Write the scene RON data to file
|
||||
File::create(save_path)
|
||||
.and_then(|mut file| file.write(serialized_scene.as_bytes()))
|
||||
.expect("Error while writing save to file");
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub(crate) fn cleanup_save(
|
||||
needs_parent_reset: Query<(Entity, &OriginalParent)>,
|
||||
mut saving_finished: EventWriter<SavingFinished>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
for (entity, original_parent) in needs_parent_reset.iter() {
|
||||
commands.entity(original_parent.0).add_child(entity);
|
||||
}
|
||||
commands.remove_resource::<StaticEntitiesStorage>();
|
||||
saving_finished.send(SavingFinished);
|
||||
}
|
||||
/*
|
||||
pub(crate) fn cleanup_save(mut world: &mut World) {
|
||||
|
||||
let mut query = world.query::<(Entity, &OriginalParent)>();
|
||||
for (mut entity, original_parent) in query.iter_mut(&mut world) {
|
||||
let e = world.entity_mut(original_parent.0);
|
||||
// .add_child(entity);
|
||||
}
|
||||
}*/
|
@ -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),
|
||||
|
@ -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,
|
||||
|
@ -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<LoadRequest>) -> bool {
|
||||
return save_requested_events.len() > 0;
|
||||
}
|
||||
|
||||
pub fn load_prepare(
|
||||
mut next_app_state: ResMut<NextState<AppState>>,
|
||||
mut next_game_state: ResMut<NextState<GameState>>,
|
||||
) {
|
||||
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<Entity> = world
|
||||
// .query_filtered::<Entity, Or<(With<Save>, With<Unload>)>>()
|
||||
.query_filtered::<Entity, With<GameWorldTag>>() // 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<Entity, With<GameWorldTag>>) {
|
||||
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<GameAssets>,
|
||||
// scenes: ResMut<Scene>,
|
||||
) {
|
||||
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<AssetServer>) {
|
||||
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<TempLoadedSceneMarker>>,
|
||||
named_entities: Query<(Entity, &Name, &Parent)>, // FIXME: very inneficient
|
||||
mut commands: Commands,
|
||||
|
||||
mut game_world: Query<(Entity, &Children), With<GameWorldTag>>,
|
||||
saveables: Query<(Entity, &Name), With<Saveable>>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
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<Saveable>>,
|
||||
mut next_app_state: ResMut<NextState<AppState>>,
|
||||
mut next_game_state: ResMut<NextState<GameState>>,
|
||||
) {
|
||||
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<TempLoadedSceneMarker>>,
|
||||
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();
|
||||
}
|
||||
}
|
@ -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::<Uuid>()
|
||||
.register_type::<Saveable>()
|
||||
.add_event::<SaveRequest>()
|
||||
.add_event::<LoadRequest>()
|
||||
|
||||
.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)
|
||||
;
|
||||
}
|
||||
}
|
@ -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<u32, String>,
|
||||
entities: HashMap<u32, Components2>
|
||||
}
|
||||
|
||||
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::<AppTypeRegistry>().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<Entity> = world
|
||||
.query_filtered::<Entity, With<Saveable>>()// 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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() }
|
||||
}
|
||||
}
|
@ -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<Input<KeyCode>>,
|
||||
save_requested_events: EventReader<SaveRequest>,
|
||||
) -> bool {
|
||||
return save_requested_events.len() > 0;
|
||||
|
||||
// return keycode.just_pressed(KeyCode::S)
|
||||
}
|
||||
|
||||
pub fn save_game(
|
||||
world: &mut World,
|
||||
// save_requested_events: EventReader<SaveRequest>,
|
||||
) {
|
||||
info!("saving");
|
||||
// world.
|
||||
/*for bli in save_requested_events.iter(){
|
||||
println!("SAAAAVE TO THISSSSS {:?}", bli.path)
|
||||
}*/
|
||||
|
||||
let saveable_entities: Vec<Entity> = world
|
||||
.query_filtered::<Entity, With<Saveable>>()
|
||||
.iter(world)
|
||||
.collect();
|
||||
|
||||
/*let static_entities: Vec<Entity> = world
|
||||
.query_filtered::<Entity, Without<Saveable>>()
|
||||
.iter(world)
|
||||
.collect();*/
|
||||
println!("saveable entities {}", saveable_entities.len());
|
||||
|
||||
let mut scene_builder = DynamicSceneBuilder::from_world(world);
|
||||
scene_builder
|
||||
.deny::<Children>()
|
||||
.deny::<Parent>()
|
||||
.deny::<ComputedVisibility>()
|
||||
.deny::<Visibility>()
|
||||
.deny::<GltfExtras>()
|
||||
.deny::<GlobalTransform>()
|
||||
.deny::<Collider>()
|
||||
.deny::<RigidBody>()
|
||||
.deny::<Saveable>()
|
||||
// camera stuff
|
||||
.deny::<Camera>()
|
||||
.deny::<CameraRenderGraph>()
|
||||
.deny::<Camera3d>()
|
||||
.deny::<Clusters>()
|
||||
.deny::<VisibleEntities>()
|
||||
.deny::<VisiblePointLights>()
|
||||
//.deny::<HasGizmoMarker>()
|
||||
.extract_entities(saveable_entities.into_iter());
|
||||
|
||||
let dyn_scene = scene_builder.build();
|
||||
let serialized_scene = dyn_scene
|
||||
.serialize_ron(world.resource::<AppTypeRegistry>())
|
||||
.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();
|
||||
}
|
@ -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<Input<KeyCode>>,
|
||||
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),
|
||||
|
@ -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)),
|
||||
)
|
||||
|
@ -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),
|
||||
|
@ -8,20 +8,22 @@ pub struct GameAssets {
|
||||
#[asset(key = "world")]
|
||||
pub world: Handle<Gltf>,
|
||||
|
||||
|
||||
|
||||
|
||||
#[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<Handle<Gltf>>,
|
||||
|
||||
|
||||
/*
|
||||
#[asset(key = "models", collection(typed, mapped))]
|
||||
pub models: HashMap<String, Handle<Gltf>>,
|
||||
|
||||
|
||||
#[asset(key = "models", collection(typed))]
|
||||
pub models: Vec<Handle<Gltf>>,*/
|
||||
}
|
||||
|
@ -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<Input<KeyCode>>,
|
||||
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),
|
||||
|
@ -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)),
|
||||
)
|
||||
|
@ -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,
|
||||
))
|
||||
|
@ -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),
|
||||
|
@ -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()
|
||||
|
@ -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<LoadRequest>) -> bool {
|
||||
return save_requested_events.len() > 0;
|
||||
}
|
||||
|
||||
pub fn load_prepare(
|
||||
mut next_app_state: ResMut<NextState<AppState>>,
|
||||
mut next_game_state: ResMut<NextState<GameState>>,
|
||||
) {
|
||||
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<Entity> = world
|
||||
// .query_filtered::<Entity, Or<(With<Save>, With<Unload>)>>()
|
||||
.query_filtered::<Entity, With<GameWorldTag>>() // 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<Entity, With<GameWorldTag>>) {
|
||||
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<GameAssets>,
|
||||
// scenes: ResMut<Scene>,
|
||||
) {
|
||||
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<AssetServer>) {
|
||||
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<TempLoadedSceneMarker>>,
|
||||
named_entities: Query<(Entity, &Name, &Parent)>, // FIXME: very inneficient
|
||||
mut commands: Commands,
|
||||
|
||||
mut game_world: Query<(Entity, &Children), With<GameWorldTag>>,
|
||||
saveables: Query<(Entity, &Name), With<Saveable>>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
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<Saveable>>,
|
||||
mut next_app_state: ResMut<NextState<AppState>>,
|
||||
mut next_game_state: ResMut<NextState<GameState>>,
|
||||
) {
|
||||
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<TempLoadedSceneMarker>>,
|
||||
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();
|
||||
}
|
||||
}
|
@ -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::<Uuid>()
|
||||
.register_type::<Saveable>()
|
||||
.add_event::<SaveRequest>()
|
||||
.add_event::<LoadRequest>()
|
||||
|
||||
.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)
|
||||
;
|
||||
}
|
||||
}
|
@ -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<u32, String>,
|
||||
entities: HashMap<u32, Components2>
|
||||
}
|
||||
|
||||
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::<AppTypeRegistry>().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<Entity> = world
|
||||
.query_filtered::<Entity, With<Saveable>>()// 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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() }
|
||||
}
|
||||
}
|
@ -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<Input<KeyCode>>,
|
||||
save_requested_events: EventReader<SaveRequest>,
|
||||
) -> bool {
|
||||
return save_requested_events.len() > 0;
|
||||
|
||||
// return keycode.just_pressed(KeyCode::S)
|
||||
}
|
||||
|
||||
pub fn save_game(
|
||||
world: &mut World,
|
||||
// save_requested_events: EventReader<SaveRequest>,
|
||||
) {
|
||||
info!("saving");
|
||||
// world.
|
||||
/*for bli in save_requested_events.iter(){
|
||||
println!("SAAAAVE TO THISSSSS {:?}", bli.path)
|
||||
}*/
|
||||
|
||||
let saveable_entities: Vec<Entity> = world
|
||||
.query_filtered::<Entity, With<Saveable>>()
|
||||
.iter(world)
|
||||
.collect();
|
||||
|
||||
/*let static_entities: Vec<Entity> = world
|
||||
.query_filtered::<Entity, Without<Saveable>>()
|
||||
.iter(world)
|
||||
.collect();*/
|
||||
println!("saveable entities {}", saveable_entities.len());
|
||||
|
||||
let mut scene_builder = DynamicSceneBuilder::from_world(world);
|
||||
scene_builder
|
||||
.deny::<Children>()
|
||||
.deny::<Parent>()
|
||||
.deny::<ComputedVisibility>()
|
||||
.deny::<Visibility>()
|
||||
.deny::<GltfExtras>()
|
||||
.deny::<GlobalTransform>()
|
||||
.deny::<Collider>()
|
||||
.deny::<RigidBody>()
|
||||
.deny::<Saveable>()
|
||||
// camera stuff
|
||||
.deny::<Camera>()
|
||||
.deny::<CameraRenderGraph>()
|
||||
.deny::<Camera3d>()
|
||||
.deny::<Clusters>()
|
||||
.deny::<VisibleEntities>()
|
||||
.deny::<VisiblePointLights>()
|
||||
//.deny::<HasGizmoMarker>()
|
||||
.extract_entities(saveable_entities.into_iter());
|
||||
|
||||
let dyn_scene = scene_builder.build();
|
||||
let serialized_scene = dyn_scene
|
||||
.serialize_ron(world.resource::<AppTypeRegistry>())
|
||||
.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();
|
||||
}
|
@ -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),
|
||||
|
@ -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,
|
||||
|
@ -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<LoadRequest>) -> bool {
|
||||
return save_requested_events.len() > 0;
|
||||
}
|
||||
|
||||
pub fn load_prepare(
|
||||
mut next_app_state: ResMut<NextState<AppState>>,
|
||||
mut next_game_state: ResMut<NextState<GameState>>,
|
||||
) {
|
||||
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<Entity> = world
|
||||
// .query_filtered::<Entity, Or<(With<Save>, With<Unload>)>>()
|
||||
.query_filtered::<Entity, With<GameWorldTag>>() // 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<Entity, With<GameWorldTag>>) {
|
||||
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<GameAssets>,
|
||||
// scenes: ResMut<Scene>,
|
||||
) {
|
||||
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<AssetServer>) {
|
||||
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<TempLoadedSceneMarker>>,
|
||||
named_entities: Query<(Entity, &Name, &Parent)>, // FIXME: very inneficient
|
||||
mut commands: Commands,
|
||||
|
||||
mut game_world: Query<(Entity, &Children), With<GameWorldTag>>,
|
||||
saveables: Query<(Entity, &Name), With<Saveable>>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
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<Saveable>>,
|
||||
mut next_app_state: ResMut<NextState<AppState>>,
|
||||
mut next_game_state: ResMut<NextState<GameState>>,
|
||||
) {
|
||||
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<TempLoadedSceneMarker>>,
|
||||
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();
|
||||
}
|
||||
}
|
@ -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::<Uuid>()
|
||||
.register_type::<Saveable>()
|
||||
.add_event::<SaveRequest>()
|
||||
.add_event::<LoadRequest>()
|
||||
|
||||
.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)
|
||||
;
|
||||
}
|
||||
}
|
@ -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<u32, String>,
|
||||
entities: HashMap<u32, Components2>
|
||||
}
|
||||
|
||||
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::<AppTypeRegistry>().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<Entity> = world
|
||||
.query_filtered::<Entity, With<Saveable>>()// 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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() }
|
||||
}
|
||||
}
|
@ -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<Input<KeyCode>>,
|
||||
save_requested_events: EventReader<SaveRequest>,
|
||||
) -> bool {
|
||||
return save_requested_events.len() > 0;
|
||||
|
||||
// return keycode.just_pressed(KeyCode::S)
|
||||
}
|
||||
|
||||
pub fn save_game(
|
||||
world: &mut World,
|
||||
// save_requested_events: EventReader<SaveRequest>,
|
||||
) {
|
||||
info!("saving");
|
||||
// world.
|
||||
/*for bli in save_requested_events.iter(){
|
||||
println!("SAAAAVE TO THISSSSS {:?}", bli.path)
|
||||
}*/
|
||||
|
||||
let saveable_entities: Vec<Entity> = world
|
||||
.query_filtered::<Entity, With<Saveable>>()
|
||||
.iter(world)
|
||||
.collect();
|
||||
|
||||
/*let static_entities: Vec<Entity> = world
|
||||
.query_filtered::<Entity, Without<Saveable>>()
|
||||
.iter(world)
|
||||
.collect();*/
|
||||
println!("saveable entities {}", saveable_entities.len());
|
||||
|
||||
let mut scene_builder = DynamicSceneBuilder::from_world(world);
|
||||
scene_builder
|
||||
.deny::<Children>()
|
||||
.deny::<Parent>()
|
||||
.deny::<ComputedVisibility>()
|
||||
.deny::<Visibility>()
|
||||
.deny::<GltfExtras>()
|
||||
.deny::<GlobalTransform>()
|
||||
.deny::<Collider>()
|
||||
.deny::<RigidBody>()
|
||||
.deny::<Saveable>()
|
||||
// camera stuff
|
||||
.deny::<Camera>()
|
||||
.deny::<CameraRenderGraph>()
|
||||
.deny::<Camera3d>()
|
||||
.deny::<Clusters>()
|
||||
.deny::<VisibleEntities>()
|
||||
.deny::<VisiblePointLights>()
|
||||
//.deny::<HasGizmoMarker>()
|
||||
.extract_entities(saveable_entities.into_iter());
|
||||
|
||||
let dyn_scene = scene_builder.build();
|
||||
let serialized_scene = dyn_scene
|
||||
.serialize_ron(world.resource::<AppTypeRegistry>())
|
||||
.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();
|
||||
}
|
@ -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),
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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<LoadRequest>) -> bool {
|
||||
return save_requested_events.len() > 0;
|
||||
}
|
||||
|
||||
pub fn load_prepare(
|
||||
mut next_app_state: ResMut<NextState<AppState>>,
|
||||
mut next_game_state: ResMut<NextState<GameState>>,
|
||||
) {
|
||||
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<Entity> = world
|
||||
// .query_filtered::<Entity, Or<(With<Save>, With<Unload>)>>()
|
||||
.query_filtered::<Entity, With<GameWorldTag>>() // 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<Entity, With<GameWorldTag>>) {
|
||||
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<GameAssets>,
|
||||
// scenes: ResMut<Scene>,
|
||||
) {
|
||||
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<AssetServer>) {
|
||||
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<TempLoadedSceneMarker>>,
|
||||
named_entities: Query<(Entity, &Name, &Parent)>, // FIXME: very inneficient
|
||||
mut commands: Commands,
|
||||
|
||||
mut game_world: Query<(Entity, &Children), With<GameWorldTag>>,
|
||||
saveables: Query<(Entity, &Name), With<Saveable>>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
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<Saveable>>,
|
||||
mut next_app_state: ResMut<NextState<AppState>>,
|
||||
mut next_game_state: ResMut<NextState<GameState>>,
|
||||
) {
|
||||
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<TempLoadedSceneMarker>>,
|
||||
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();
|
||||
}
|
||||
}
|
@ -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::<Uuid>()
|
||||
.register_type::<Saveable>()
|
||||
.add_event::<SaveRequest>()
|
||||
.add_event::<LoadRequest>()
|
||||
|
||||
.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)
|
||||
;
|
||||
}
|
||||
}
|
@ -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<u32, String>,
|
||||
entities: HashMap<u32, Components2>
|
||||
}
|
||||
|
||||
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::<AppTypeRegistry>().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<Entity> = world
|
||||
.query_filtered::<Entity, With<Saveable>>()// 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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() }
|
||||
}
|
||||
}
|
@ -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<Input<KeyCode>>,
|
||||
save_requested_events: EventReader<SaveRequest>,
|
||||
) -> bool {
|
||||
return save_requested_events.len() > 0;
|
||||
|
||||
// return keycode.just_pressed(KeyCode::S)
|
||||
}
|
||||
|
||||
pub fn save_game(
|
||||
world: &mut World,
|
||||
// save_requested_events: EventReader<SaveRequest>,
|
||||
) {
|
||||
info!("saving");
|
||||
// world.
|
||||
/*for bli in save_requested_events.iter(){
|
||||
println!("SAAAAVE TO THISSSSS {:?}", bli.path)
|
||||
}*/
|
||||
|
||||
let saveable_entities: Vec<Entity> = world
|
||||
.query_filtered::<Entity, With<Saveable>>()
|
||||
.iter(world)
|
||||
.collect();
|
||||
|
||||
/*let static_entities: Vec<Entity> = world
|
||||
.query_filtered::<Entity, Without<Saveable>>()
|
||||
.iter(world)
|
||||
.collect();*/
|
||||
println!("saveable entities {}", saveable_entities.len());
|
||||
|
||||
let mut scene_builder = DynamicSceneBuilder::from_world(world);
|
||||
scene_builder
|
||||
.deny::<Children>()
|
||||
.deny::<Parent>()
|
||||
.deny::<ComputedVisibility>()
|
||||
.deny::<Visibility>()
|
||||
.deny::<GltfExtras>()
|
||||
.deny::<GlobalTransform>()
|
||||
.deny::<Collider>()
|
||||
.deny::<RigidBody>()
|
||||
.deny::<Saveable>()
|
||||
// camera stuff
|
||||
.deny::<Camera>()
|
||||
.deny::<CameraRenderGraph>()
|
||||
.deny::<Camera3d>()
|
||||
.deny::<Clusters>()
|
||||
.deny::<VisibleEntities>()
|
||||
.deny::<VisiblePointLights>()
|
||||
//.deny::<HasGizmoMarker>()
|
||||
.extract_entities(saveable_entities.into_iter());
|
||||
|
||||
let dyn_scene = scene_builder.build();
|
||||
let serialized_scene = dyn_scene
|
||||
.serialize_ron(world.resource::<AppTypeRegistry>())
|
||||
.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();
|
||||
}
|
@ -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),
|
||||
|
17
examples/bevy_gltf_save_load/basic/Cargo.toml
Normal file
17
examples/bevy_gltf_save_load/basic/Cargo.toml
Normal file
@ -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"
|
16
examples/bevy_gltf_save_load/basic/README.md
Normal file
16
examples/bevy_gltf_save_load/basic/README.md
Normal file
@ -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
|
||||
```
|
||||
|
@ -0,0 +1 @@
|
||||
({})
|
@ -0,0 +1,8 @@
|
||||
({
|
||||
"world":File (path: "models/World.glb"),
|
||||
"world_dynamic":File (path: "models/World_dynamic.glb"),
|
||||
|
||||
"models": Folder (
|
||||
path: "models/library",
|
||||
),
|
||||
})
|
BIN
examples/bevy_gltf_save_load/basic/assets/basic.blend
Normal file
BIN
examples/bevy_gltf_save_load/basic/assets/basic.blend
Normal file
Binary file not shown.
BIN
examples/bevy_gltf_save_load/basic/assets/models/World.glb
Normal file
BIN
examples/bevy_gltf_save_load/basic/assets/models/World.glb
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2536
examples/bevy_gltf_save_load/basic/assets/scenes/save.scn.ron
Normal file
2536
examples/bevy_gltf_save_load/basic/assets/scenes/save.scn.ron
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,5 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy_asset_loader::prelude::*;
|
||||
|
||||
#[derive(AssetCollection, Resource)]
|
||||
pub struct CoreAssets {}
|
16
examples/bevy_gltf_save_load/basic/src/assets/assets_game.rs
Normal file
16
examples/bevy_gltf_save_load/basic/src/assets/assets_game.rs
Normal file
@ -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<Gltf>,
|
||||
|
||||
#[asset(key = "world_dynamic")]
|
||||
pub world_dynamic: Handle<Gltf>,
|
||||
|
||||
#[asset(key = "models", collection(typed, mapped))]
|
||||
pub models: HashMap<String, Handle<Gltf>>,
|
||||
}
|
35
examples/bevy_gltf_save_load/basic/src/assets/mod.rs
Normal file
35
examples/bevy_gltf_save_load/basic/src/assets/mod.rs
Normal file
@ -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);
|
||||
}
|
||||
}
|
@ -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<Camera>, With<CameraTrackingOffset>)>,
|
||||
) {
|
||||
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()
|
||||
});
|
||||
}
|
||||
}
|
@ -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<Camera>,
|
||||
With<CameraTrackingOffset>,
|
||||
Without<CameraTrackable>,
|
||||
),
|
||||
>,
|
||||
camera_tracked: Query<&Transform, With<CameraTrackable>>,
|
||||
) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
24
examples/bevy_gltf_save_load/basic/src/core/camera/mod.rs
Normal file
24
examples/bevy_gltf_save_load/basic/src/core/camera/mod.rs
Normal file
@ -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::<CameraTrackable>()
|
||||
.register_type::<CameraTracking>()
|
||||
.register_type::<CameraTrackingOffset>()
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
camera_replace_proxies.after(GltfBlueprintsSet::AfterSpawn),
|
||||
camera_track,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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<DirectionalLight>>,
|
||||
mut added_spotlights: Query<&mut SpotLight, Added<SpotLight>>,
|
||||
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;
|
||||
}
|
||||
}
|
18
examples/bevy_gltf_save_load/basic/src/core/lighting/mod.rs
Normal file
18
examples/bevy_gltf_save_load/basic/src/core/lighting/mod.rs
Normal file
@ -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::<NotShadowCaster>()
|
||||
|
||||
.add_systems(PreUpdate, lighting_replace_proxies) // FIXME: you should actually run this in a specific state most likely
|
||||
;
|
||||
}
|
||||
}
|
62
examples/bevy_gltf_save_load/basic/src/core/mod.rs
Normal file
62
examples/bevy_gltf_save_load/basic/src/core/mod.rs
Normal file
@ -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::<Name>(),
|
||||
TypeId::of::<Transform>(),
|
||||
TypeId::of::<Velocity>(),
|
||||
TypeId::of::<InheritedVisibility>(),
|
||||
TypeId::of::<Camera>(),
|
||||
TypeId::of::<Camera3d>(),
|
||||
TypeId::of::<Tonemapping>(),
|
||||
TypeId::of::<CameraTrackingOffset>(),
|
||||
TypeId::of::<Projection>(),
|
||||
TypeId::of::<CameraRenderGraph>(),
|
||||
TypeId::of::<Frustum>(),
|
||||
TypeId::of::<GlobalTransform>(),
|
||||
TypeId::of::<VisibleEntities>(),
|
||||
TypeId::of::<Pickable>(),
|
||||
])),
|
||||
..Default::default()
|
||||
},
|
||||
BlueprintsPlugin {
|
||||
library_folder: "models/library".into(),
|
||||
format: GltfFormat::GLB,
|
||||
aabbs: true,
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
@ -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<RapierConfiguration>) {
|
||||
info!("pausing physics");
|
||||
physics_config.physics_pipeline_active = false;
|
||||
}
|
||||
|
||||
pub fn resume_physics(mut physics_config: ResMut<RapierConfiguration>) {
|
||||
info!("unpausing physics");
|
||||
physics_config.physics_pipeline_active = true;
|
||||
}
|
||||
|
||||
pub fn toggle_physics_debug(
|
||||
mut debug_config: ResMut<DebugRenderContext>,
|
||||
keycode: Res<Input<KeyCode>>,
|
||||
) {
|
||||
if keycode.just_pressed(KeyCode::D) {
|
||||
debug_config.enabled = !debug_config.enabled;
|
||||
}
|
||||
}
|
38
examples/bevy_gltf_save_load/basic/src/core/physics/mod.rs
Normal file
38
examples/bevy_gltf_save_load/basic/src/core/physics/mod.rs
Normal file
@ -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::<NoUserData>::default(),
|
||||
RapierDebugRenderPlugin::default(),
|
||||
))
|
||||
.register_type::<AutoAABBCollider>()
|
||||
.register_type::<physics_replace_proxies::Collider>()
|
||||
// find a way to make serde's stuff serializable
|
||||
// .register_type::<bevy_rapier3d::dynamics::CoefficientCombineRule>()
|
||||
//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);
|
||||
}
|
||||
}
|
@ -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<Assets<Mesh>>,
|
||||
mesh_handles: Query<&Handle<Mesh>>,
|
||||
mut proxy_colliders: Query<
|
||||
(Entity, &Collider, &Name, &mut Visibility),
|
||||
(Without<RapierCollider>, Added<Collider>),
|
||||
>,
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
175
examples/bevy_gltf_save_load/basic/src/core/physics/utils.rs
Normal file
175
examples/bevy_gltf_save_load/basic/src/core/physics/utils.rs
Normal file
@ -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<MeshVertexAttributeId>) -> &mut Vec<[f32; 3]>;
|
||||
fn search_in_children<'a>(
|
||||
parent: Entity,
|
||||
children: &'a Query<&Children>,
|
||||
meshes: &'a Assets<Mesh>,
|
||||
mesh_handles: &'a Query<&Handle<Mesh>>,
|
||||
) -> 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<MeshVertexAttributeId>) -> &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>,
|
||||
mesh_handles: &'a Query<&Handle<Mesh>>,
|
||||
) -> 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,
|
||||
}
|
||||
}
|
||||
}
|
@ -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<MeshVertexAttributeId>) -> &mut Vec<[f32; 3]>;
|
||||
fn search_in_children<'a>(
|
||||
children: &'a Children,
|
||||
meshes: &'a Assets<Mesh>,
|
||||
mesh_handles: &'a Query<&Handle<Mesh>>,
|
||||
) -> (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<MeshVertexAttributeId>) -> &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>,
|
||||
mesh_handles: &'a Query<&Handle<Mesh>>,
|
||||
) -> (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)
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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<Dependant>, Without<Dependency>)>,
|
||||
) {
|
||||
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::<Dependant>(), std::any::type_name::<Dependency>());
|
||||
}
|
||||
}
|
203
examples/bevy_gltf_save_load/basic/src/game/in_game.rs
Normal file
203
examples/bevy_gltf_save_load/basic/src/game/in_game.rs
Normal file
@ -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<NextState<GameState>>) {
|
||||
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<Entity, With<GameWorldTag>>) {
|
||||
for e in gameworlds.iter() {
|
||||
info!("--loading: despawn old world/level");
|
||||
commands.entity(e).despawn_recursive();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn should_reset(keycode: Res<Input<KeyCode>>) -> bool {
|
||||
return keycode.just_pressed(KeyCode::N);
|
||||
}
|
||||
|
||||
pub fn spawn_test(
|
||||
keycode: Res<Input<KeyCode>>,
|
||||
mut dynamic_entities_world: Query<Entity, With<DynamicEntitiesRoot>>,
|
||||
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<Input<KeyCode>>,
|
||||
mut commands: Commands,
|
||||
|
||||
mut dynamic_entities_world: Query<Entity, With<DynamicEntitiesRoot>>,
|
||||
) {
|
||||
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<Input<KeyCode>>,
|
||||
players: Query<Entity, With<Player>>,
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<Entity, With<InGameLoading>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
for entity in in_main_menu.iter() {
|
||||
commands.entity(entity).despawn_recursive();
|
||||
}
|
||||
}
|
@ -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<Entity, With<InGameSaving>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
for entity in in_main_menu.iter() {
|
||||
commands.entity(entity).despawn_recursive();
|
||||
}
|
||||
}
|
116
examples/bevy_gltf_save_load/basic/src/game/in_main_menu.rs
Normal file
116
examples/bevy_gltf_save_load/basic/src/game/in_main_menu.rs
Normal file
@ -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<Entity, With<InMainMenu>>, mut commands: Commands) {
|
||||
for entity in in_main_menu.iter() {
|
||||
commands.entity(entity).despawn_recursive();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main_menu(
|
||||
keycode: Res<Input<KeyCode>>,
|
||||
|
||||
mut next_app_state: ResMut<NextState<AppState>>,
|
||||
// mut next_game_state: ResMut<NextState<GameState>>,
|
||||
// mut save_requested_events: EventWriter<SaveRequest>,
|
||||
// mut load_requested_events: EventWriter<LoadRequest>,
|
||||
) {
|
||||
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() })
|
||||
}
|
||||
}
|
171
examples/bevy_gltf_save_load/basic/src/game/mod.rs
Normal file
171
examples/bevy_gltf_save_load/basic/src/game/mod.rs
Normal file
@ -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<Input<KeyCode>>,
|
||||
mut players: Query<&mut Transform, With<Player>>,
|
||||
) {
|
||||
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<SaveRequest>,
|
||||
keycode: Res<Input<KeyCode>>,
|
||||
|
||||
current_state: Res<State<GameState>>,
|
||||
mut next_game_state: ResMut<NextState<GameState>>,
|
||||
) {
|
||||
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<SavingFinished>,
|
||||
mut next_game_state: ResMut<NextState<GameState>>,
|
||||
) {
|
||||
for _ in saving_finished.read() {
|
||||
next_game_state.set(GameState::InGame);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_load(
|
||||
mut load_requests: EventWriter<LoadRequest>,
|
||||
keycode: Res<Input<KeyCode>>,
|
||||
current_state: Res<State<GameState>>,
|
||||
mut next_game_state: ResMut<NextState<GameState>>,
|
||||
) {
|
||||
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<LoadingFinished>,
|
||||
mut next_game_state: ResMut<NextState<GameState>>,
|
||||
) {
|
||||
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::<Interactible>()
|
||||
.register_type::<SoundMaterial>()
|
||||
.register_type::<Player>()
|
||||
.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, ShouldBeWithPlayer>,
|
||||
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);
|
||||
}
|
||||
}
|
34
examples/bevy_gltf_save_load/basic/src/game/picking.rs
Normal file
34
examples/bevy_gltf_save_load/basic/src/game/picking.rs
Normal file
@ -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<Player>>,
|
||||
pickables: Query<(Entity, &GlobalTransform), With<Pickable>>,
|
||||
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::<Pickable>()
|
||||
.add_systems(Update, (picking.after(GltfBlueprintsSet::AfterSpawn),));
|
||||
}
|
||||
}
|
33
examples/bevy_gltf_save_load/basic/src/main.rs
Normal file
33
examples/bevy_gltf_save_load/basic/src/main.rs
Normal file
@ -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();
|
||||
}
|
57
examples/bevy_gltf_save_load/basic/src/state.rs
Normal file
57
examples/bevy_gltf_save_load/basic/src/state.rs
Normal file
@ -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::<AppState>().add_state::<GameState>();
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user