chore(bevy_gltf_components): Update to Bevy 0.12 (#32)

* chore(bevy_gltf_components): updated code for bevy_main/ v0.12
* refactor(examples): cleanups & tweaks
* added compatibility tables
This commit is contained in:
Mark Moissette 2023-11-11 22:58:00 +01:00 committed by GitHub
parent d06aa0042e
commit c4e83655f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 636 additions and 555 deletions

565
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -10,16 +10,15 @@ members = [
"crates/bevy_gltf_blueprints", "crates/bevy_gltf_blueprints",
] ]
[dev-dependencies] [dev-dependencies]
bevy="0.11" bevy="0.12"
bevy_rapier3d = { version = "0.22.0", features = [ "serde-serialize", "debug-render-3d", "enhanced-determinism"] } bevy_asset_loader = { version = "0.18", features = ["standard_dynamic_assets" ]}
bevy_editor_pls = { version = "0.5.0" } bevy_rapier3d = { version = "0.23.0", features = [ "serde-serialize", "debug-render-3d", "enhanced-determinism"] }
bevy_asset_loader = { version = "0.17.0", features = ["standard_dynamic_assets" ]} #version = "0.16", bevy_editor_pls = { version = "0.6" }
rand = "0.8.5" rand = "0.8.5"
[dependencies] [dependencies]
bevy = { version = "0.11", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf"] } bevy = { version = "0.12", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf"] }
bevy_gltf_components = { path = "crates/bevy_gltf_components" } bevy_gltf_components = { path = "crates/bevy_gltf_components" }
bevy_gltf_blueprints = { path = "crates/bevy_gltf_blueprints" } bevy_gltf_blueprints = { path = "crates/bevy_gltf_blueprints" }

View File

@ -128,11 +128,9 @@ you will get a warning **per entity**
## Limitations / issues ## Limitations / issues
- some components have to be defined in ```text``` in Blender, might try using the AppTypeRegistry and some Python code on the Blender side for a nicer UI (although this loses the "fast & easy, no tooling" approach) - some components have to be defined in ```text``` in Blender, might try using the AppTypeRegistry and some Python code on the Blender side for a nicer UI (although this loses the "fast & easy, no tooling" approach)
- Some of `bevy_rapier`/physics code / ways to define colliders could perhaps be done better within Blender (currently it also goes via RON) - Some of `bevy_rapier`/physics code / ways to define colliders could perhaps be done better within Blender (currently it also goes via RON)
- there seem to be some random system ordering issues that I am still investigating (only when replacing proxy components, no breaking bugs, just restarting your Bevy app is enough)
## Future work ## Future work
- I have a number of other tools/ code helpers that I have not yet included here, because they need cleanup/ might make this example too complex - I have a number of other tools/ code helpers that I have not yet included here, because they need cleanup/ might make this example too complex
* simplified animation logic: ie instead of having to manually specify the animations you need from a gltf file, it is integrated with the spawning system above, which creates a ```Animations``` component in all entities that have an ```AnimationPlayer``` and you can simply query for both to easilly control your animations per entity.
## Credits ## Credits

View File

@ -1,5 +1,5 @@
({ ({
"world":File (path: "advanced/models/World.glb#Scene0"), "world":File (path: "advanced/models/World.glb"),
"models": Folder ( "models": Folder (
path: "advanced/models/library", path: "advanced/models/library",
), ),

View File

@ -1,5 +1,5 @@
({ ({
"world":File (path: "animation/models/Level1.glb#Scene0"), "world":File (path: "animation/models/Level1.glb"),
"models": Folder ( "models": Folder (
path: "animation/models/library", path: "animation/models/library",
), ),

View File

@ -1,6 +1,6 @@
[package] [package]
name = "bevy_gltf_blueprints" name = "bevy_gltf_blueprints"
version = "0.2.1" version = "0.3.0"
authors = ["Mark 'kaosat-dev' Moissette"] 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." 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" homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"
@ -11,8 +11,8 @@ edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
[dev-dependencies] [dev-dependencies]
bevy = { version = "0.11", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf", "bevy_animation", "animation"] } bevy = { version = "0.12", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf", "bevy_animation", "animation"] }
[dependencies] [dependencies]
bevy_gltf_components = "0.1" bevy_gltf_components = "0.2"
bevy = { version = "0.11", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf", "bevy_animation", "animation"] } bevy = { version = "0.12", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf", "bevy_animation", "animation"] }

View File

@ -25,8 +25,8 @@ Here's a minimal usage example:
```toml ```toml
# Cargo.toml # Cargo.toml
[dependencies] [dependencies]
bevy="0.11" bevy="0.12"
bevy_gltf_blueprints = { version = "0.2"} bevy_gltf_blueprints = { version = "0.3"}
``` ```
@ -64,7 +64,7 @@ fn spawn_blueprint(
Add the following to your `[dependencies]` section in `Cargo.toml`: Add the following to your `[dependencies]` section in `Cargo.toml`:
```toml ```toml
bevy_gltf_blueprints = "0.2" bevy_gltf_blueprints = "0.3"
``` ```
Or use `cargo add`: Or use `cargo add`:
@ -246,6 +246,19 @@ https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/example
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/animation
## 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_blueprints` versions:
| `bevy_gltf_blueprints` | `bevy` |
| :-- | :-- |
| `0.3` | `0.12` |
| `0.1 -0.2` | `0.11` |
| branch `main` | `0.12` |
| branch `bevy_main` | `main` |
## License ## License
This crate, all its code, contents & assets is Dual-licensed under either of This crate, all its code, contents & assets is Dual-licensed under either of

View File

@ -58,14 +58,15 @@ pub(crate) fn spawn_from_blueprints(
Path::new(&blueprints_config.library_folder).join(Path::new(model_file_name.as_str())); Path::new(&blueprints_config.library_folder).join(Path::new(model_file_name.as_str()));
debug!("attempting to spawn {:?}", model_path); debug!("attempting to spawn {:?}", model_path);
let scene: Handle<Gltf> = asset_server.load(model_path); let model_handle: Handle<Gltf> = asset_server.load(model_path);
let world = game_world.single_mut(); let world = game_world.single_mut();
let world = world.1[0]; // FIXME: dangerous hack because our gltf data have a single child like this, but might not always be the case let world = world.1[0]; // FIXME: dangerous hack because our gltf data have a single child like this, but might not always be the case
let gltf = assets_gltf let gltf = assets_gltf
.get(&scene) .get(&model_handle)
.expect("this gltf should have been loaded"); .expect("this gltf should have been loaded");
// WARNING we work under the assumtion that there is ONLY ONE named scene, and that the first one is the right one // WARNING we work under the assumtion that there is ONLY ONE named scene, and that the first one is the right one
let main_scene_name = gltf let main_scene_name = gltf
.named_scenes .named_scenes

View File

@ -1,6 +1,6 @@
[package] [package]
name = "bevy_gltf_components" name = "bevy_gltf_components"
version = "0.1.3" version = "0.2.0"
authors = ["Mark 'kaosat-dev' Moissette"] 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." 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" homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"
@ -11,9 +11,9 @@ edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
[dev-dependencies] [dev-dependencies]
bevy = { version = "0.11", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf"] } bevy = { version = "0.12", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf"] }
[dependencies] [dependencies]
bevy = { version = "0.11", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf"] } bevy = { version = "0.12", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf"] }
serde = "1.0.188" serde = "1.0.188"
ron = "0.8.1" ron = "0.8.1"

View File

@ -23,8 +23,8 @@ Here's a minimal usage example:
```toml ```toml
# Cargo.toml # Cargo.toml
[dependencies] [dependencies]
bevy="0.11" bevy="0.12"
bevy_gltf_components = { version = "0.1"} bevy_gltf_components = { version = "0.2"}
``` ```
@ -88,6 +88,20 @@ Typically , the order of systems should be
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/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_components` versions:
| `bevy_gltf_components` | `bevy` |
| :-- | :-- |
| `0.2` | `0.12` |
| `0.1` | `0.11` |
| branch `main` | `0.12` |
| branch `bevy_main` | `main` |
## License ## License
This crate, all its code, contents & assets is Dual-licensed under either of This crate, all its code, contents & assets is Dual-licensed under either of

View File

@ -5,24 +5,167 @@ use serde::de::DeserializeSeed;
use bevy::ecs::{entity::Entity, reflect::ReflectComponent}; use bevy::ecs::{entity::Entity, reflect::ReflectComponent};
use bevy::gltf::{Gltf, GltfExtras}; use bevy::gltf::{Gltf, GltfExtras};
use bevy::prelude::{debug, info, warn, Assets, Name, Parent, ResMut}; use bevy::reflect::serde::UntypedReflectDeserializer; // ReflectSerializer
use bevy::reflect::serde::UntypedReflectDeserializer; use bevy::reflect::{Reflect, TypeInfo, TypeRegistry};
use bevy::reflect::{Reflect, TypeInfo, TypeRegistryInternal};
use bevy::scene::Scene; use bevy::scene::Scene;
use bevy::utils::HashMap; use bevy::utils::HashMap;
use bevy::{
log::{debug, info, warn},
prelude::{Assets, Name, Parent, ResMut},
};
use super::capitalize_first_letter; use super::capitalize_first_letter;
pub fn ronstring_to_reflect_component(
ron_string: &String,
type_registry: &TypeRegistry,
) -> Vec<Box<dyn Reflect>> {
let lookup: HashMap<String, Value> = ron::from_str(ron_string.as_str()).unwrap();
let mut components: Vec<Box<dyn Reflect>> = Vec::new();
for (key, value) in lookup.into_iter() {
let type_string = key.replace("component: ", "").trim().to_string();
let capitalized_type_name = capitalize_first_letter(type_string.as_str());
let mut parsed_value: String;
match value.clone() {
Value::String(str) => {
parsed_value = str;
}
_ => parsed_value = ron::to_string(&value).unwrap().to_string(),
}
if let Some(type_registration) =
type_registry.get_with_short_type_path(capitalized_type_name.as_str())
{
debug!("TYPE INFO {:?}", type_registration.type_info());
match type_registration.type_info() {
TypeInfo::TupleStruct(info) => {
// we handle tupple strucs with only one field differently, as Blender's custom properties with custom ui (float, int, bool, etc) always give us a tupple struct
if info.field_len() == 1 {
let field = info
.field_at(0)
.expect("we should always have at least one field here");
let field_name = field.type_path();
// TODO: find a way to cast with typeId instead of this matching
/*match field.type_id(){
TypeId::of::<f32>() => {
println!("WE HAVE A f32");
}
}
Vec3 => {
println!("WE HAVE A VEC3");
let bla:Vec3 = ron::from_str(&parsed_value).unwrap();
println!("bla {}", bla)
}
_ =>{}
}*/
let mut formated = parsed_value.clone();
match field_name {
"f32" => {
formated = parsed_value.parse::<f32>().unwrap().to_string();
}
"f64" => {
formated = parsed_value.parse::<f64>().unwrap().to_string();
}
"u8" => {
formated = parsed_value.parse::<u8>().unwrap().to_string();
}
"u16" => {
formated = parsed_value.parse::<u16>().unwrap().to_string();
}
"u32" => {
formated = parsed_value.parse::<u32>().unwrap().to_string();
}
"u64" => {
formated = parsed_value.parse::<u64>().unwrap().to_string();
}
"u128" => {
formated = parsed_value.parse::<u128>().unwrap().to_string();
}
"glam::Vec2" => {
let parsed: Vec<f32> = ron::from_str(&parsed_value).unwrap();
formated = format!("(x:{},y:{})", parsed[0], parsed[1]);
}
"glam::Vec3" => {
let parsed: Vec<f32> = ron::from_str(&parsed_value).unwrap();
formated =
format!("(x:{},y:{},z:{})", parsed[0], parsed[1], parsed[2]);
}
"bevy_render::color::Color" => {
let parsed: Vec<f32> = ron::from_str(&parsed_value).unwrap();
if parsed.len() == 3 {
formated = format!(
"Rgba(red:{},green:{},blue:{}, alpha: 1.0)",
parsed[0], parsed[1], parsed[2]
);
}
if parsed.len() == 4 {
formated = format!(
"Rgba(red:{},green:{},blue:{}, alpha:{})",
parsed[0], parsed[1], parsed[2], parsed[3]
);
}
}
_ => {}
}
parsed_value = format!("({formated})");
}
}
_ => {}
}
// println!("parsed value {}",parsed_value);
if parsed_value.is_empty() {
parsed_value = "()".to_string();
}
let ron_string = format!(
"{{ \"{}\":{} }}",
type_registration.type_info().type_path(),
parsed_value
);
// usefull to determine what an entity looks like Serialized
/*let test_struct = Enemy::default();
let serializer = ReflectSerializer::new(&test_struct, &type_registry);
let serialized =
ron::ser::to_string_pretty(&serializer, ron::ser::PrettyConfig::default()).unwrap();
println!("serialized Component {}", serialized);*/
debug!("component data ron string {}", ron_string);
let mut deserializer = ron::Deserializer::from_str(ron_string.as_str()).unwrap();
let reflect_deserializer = UntypedReflectDeserializer::new(type_registry);
let component = reflect_deserializer.deserialize(&mut deserializer).expect(
format!(
"failed to deserialize component {} with value: {:?}",
key, value
)
.as_str(),
);
debug!("component {:?}", component);
debug!("real type {:?}", component.get_represented_type_info());
components.push(component);
debug!("found type registration for {}", capitalized_type_name);
} else {
warn!("no type registration for {}", capitalized_type_name);
}
}
components
}
/// main function: injects components into each entity in gltf files that have gltf_extras, using reflection /// main function: injects components into each entity in gltf files that have gltf_extras, using reflection
pub fn gltf_extras_to_components( pub fn gltf_extras_to_components(
gltf: &mut Gltf, gltf: &mut Gltf,
scenes: &mut ResMut<Assets<Scene>>, scenes: &mut ResMut<Assets<Scene>>,
type_registry: impl Deref<Target = TypeRegistryInternal>, type_registry: impl Deref<Target = TypeRegistry>,
gltf_name: &str,
) { ) {
let mut added_components = 0; let mut added_components = 0;
for (_name, scene) in &gltf.named_scenes { for (_name, scene) in &gltf.named_scenes {
debug!("gltf: {:?} scene name {:?}", gltf_name, _name); debug!("gltf: scene name {:?}", _name);
let scene = scenes.get_mut(scene).unwrap(); let scene = scenes.get_mut(scene).unwrap();
@ -81,16 +224,22 @@ pub fn gltf_extras_to_components(
} }
for component in components { for component in components {
let mut entity_mut = scene.world.entity_mut(entity); let mut entity_mut = scene.world.entity_mut(entity);
debug!("------adding {} {:?}", component.type_name(), component); debug!(
"------adding {} {:?}",
component.get_represented_type_info().unwrap().type_path(),
component
);
let component_type_path =
component.get_represented_type_info().unwrap().type_path();
type_registry type_registry
.get_with_name(component.type_name()) .get_with_type_path(component_type_path)
.unwrap() // Component was successfully deserialized, it has to be in the registry .unwrap() // Component was successfully deserialized, it has to be in the registry
.data::<ReflectComponent>() .data::<ReflectComponent>()
.unwrap() // Hopefully, the component deserializer ensures those are components .unwrap() // Hopefully, the component deserializer ensures those are components
.insert(&mut entity_mut, &*component); .insert(&mut entity_mut, &*component);
// info!("all components {:?}", scene.world.entity(entity).archetype().components()); // debug!("all components {:?}", scene.world.entity(entity).archetype().components());
// scene.world.components(). // scene.world.components().
// TODO: how can we insert any additional components "by hand" here ? // TODO: how can we insert any additional components "by hand" here ?
} }
@ -103,142 +252,5 @@ pub fn gltf_extras_to_components(
} }
} }
} }
info!("done extracting gltf_extras /n"); info!("done injecting components from gltf_extras /n");
}
pub fn ronstring_to_reflect_component(
ron_string: &String,
type_registry: &TypeRegistryInternal,
) -> Vec<Box<dyn Reflect>> {
let lookup: HashMap<String, Value> = ron::from_str(ron_string.as_str()).unwrap();
let mut components: Vec<Box<dyn Reflect>> = Vec::new();
for (key, value) in lookup.into_iter() {
let type_string = key.replace("component: ", "").trim().to_string();
let capitalized_type_name = capitalize_first_letter(type_string.as_str());
let mut parsed_value: String;
match value.clone() {
Value::String(str) => {
parsed_value = str;
}
_ => parsed_value = ron::to_string(&value).unwrap().to_string(),
}
if let Some(type_registration) =
type_registry.get_with_short_name(capitalized_type_name.as_str())
{
// println!("TYPE INFO {:?}", type_registration.type_info());
match type_registration.type_info() {
TypeInfo::TupleStruct(info) => {
// we handle tupple strucs with only one field differently, as Blender's custom properties with custom ui (float, int, bool, etc) always give us a tupple struct
if info.field_len() == 1 {
let field = info
.field_at(0)
.expect("we should always have at least one field here");
let field_name = field.type_name();
// TODO: find a way to cast with typeId instead of this matching
/*match field.type_id(){
TypeId::of::<f32>() => {
println!("WE HAVE A f32");
}
}
Vec3 => {
println!("WE HAVE A VEC3");
let bla:Vec3 = ron::from_str(&parsed_value).unwrap();
println!("bla {}", bla)
}
_ =>{}
}*/
let mut formated = parsed_value.clone();
match field_name {
"f32" => {
formated = parsed_value.parse::<f32>().unwrap().to_string();
}
"f64" => {
formated = parsed_value.parse::<f64>().unwrap().to_string();
}
"u8" => {
formated = parsed_value.parse::<u8>().unwrap().to_string();
}
"u16" => {
formated = parsed_value.parse::<u16>().unwrap().to_string();
}
"u32" => {
formated = parsed_value.parse::<u32>().unwrap().to_string();
}
"u64" => {
formated = parsed_value.parse::<u64>().unwrap().to_string();
}
"u128" => {
formated = parsed_value.parse::<u128>().unwrap().to_string();
}
"glam::f32::vec2::Vec2" => {
let parsed: Vec<f32> = ron::from_str(&parsed_value).unwrap();
formated = format!("(x:{},y:{})", parsed[0], parsed[1]);
}
"glam::f32::vec3::Vec3" => {
let parsed: Vec<f32> = ron::from_str(&parsed_value).unwrap();
formated =
format!("(x:{},y:{},z:{})", parsed[0], parsed[1], parsed[2]);
}
"bevy_render::color::Color" => {
let parsed: Vec<f32> = ron::from_str(&parsed_value).unwrap();
if parsed.len() == 3 {
formated = format!(
"Rgba(red:{},green:{},blue:{}, alpha: 1.0)",
parsed[0], parsed[1], parsed[2]
);
}
if parsed.len() == 4 {
formated = format!(
"Rgba(red:{},green:{},blue:{}, alpha:{})",
parsed[0], parsed[1], parsed[2], parsed[3]
);
}
}
_ => {}
}
parsed_value = format!("({formated})");
}
}
_ => {}
}
// println!("parsed value {}",parsed_value);
if parsed_value.is_empty() {
parsed_value = "()".to_string();
}
let ron_string = format!(
"{{ \"{}\":{} }}",
type_registration.type_name(),
parsed_value
);
// usefull to determine what an entity looks like Serialized
/*let test_struct = TuppleTestStr::default();
let serializer = ReflectSerializer::new(&test_struct, &type_registry);
let serialized =
ron::ser::to_string_pretty(&serializer, ron::ser::PrettyConfig::default()).unwrap();
println!("serialized Component {}", serialized);*/
// println!("component data ron string {}", ron_string);
let mut deserializer = ron::Deserializer::from_str(ron_string.as_str()).unwrap();
let reflect_deserializer = UntypedReflectDeserializer::new(type_registry);
let component = reflect_deserializer.deserialize(&mut deserializer).expect(
format!(
"failed to deserialize component {} with value: {:?}",
key, value
)
.as_str(),
);
components.push(component);
debug!("found type registration for {}", capitalized_type_name);
} else {
warn!("no type registration for {}", capitalized_type_name);
}
}
components
} }

View File

@ -2,7 +2,7 @@ use bevy::gltf::Gltf;
use bevy::utils::HashSet; use bevy::utils::HashSet;
use bevy::{asset::LoadState, prelude::*}; use bevy::{asset::LoadState, prelude::*};
use super::gltf_extras_to_components; use crate::gltf_extras_to_components;
#[derive(Resource)] #[derive(Resource)]
/// component to keep track of gltfs' loading state /// component to keep track of gltfs' loading state
@ -18,7 +18,7 @@ impl GltfLoadingTracker {
loading_gltfs: HashSet::new(), loading_gltfs: HashSet::new(),
} }
} }
pub fn add_scene(&mut self, handle: Handle<Gltf>) { pub fn add_gltf(&mut self, handle: Handle<Gltf>) {
self.loading_gltfs.insert(handle); self.loading_gltfs.insert(handle);
} }
} }
@ -26,10 +26,14 @@ impl GltfLoadingTracker {
pub fn track_new_gltf( pub fn track_new_gltf(
mut tracker: ResMut<GltfLoadingTracker>, mut tracker: ResMut<GltfLoadingTracker>,
mut events: EventReader<AssetEvent<Gltf>>, mut events: EventReader<AssetEvent<Gltf>>,
asset_server: Res<AssetServer>,
) { ) {
for event in events.iter() { for event in events.read() {
if let AssetEvent::Created { handle } = event { if let AssetEvent::Added { id } = event {
tracker.add_scene(handle.clone()); let handle = asset_server
.get_id_handle(*id)
.expect("this gltf should have been loaded");
tracker.add_gltf(handle.clone());
debug!("gltf created {:?}", handle.clone()); debug!("gltf created {:?}", handle.clone());
} }
} }
@ -38,21 +42,23 @@ pub fn track_new_gltf(
pub fn process_loaded_scenes( pub fn process_loaded_scenes(
mut gltfs: ResMut<Assets<Gltf>>, mut gltfs: ResMut<Assets<Gltf>>,
mut scenes: ResMut<Assets<Scene>>,
mut tracker: ResMut<GltfLoadingTracker>, mut tracker: ResMut<GltfLoadingTracker>,
mut scenes: ResMut<Assets<Scene>>,
app_type_registry: Res<AppTypeRegistry>, app_type_registry: Res<AppTypeRegistry>,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
) { ) {
let mut loaded_gltfs = Vec::new(); let mut loaded_gltfs = Vec::new();
for gltf in &tracker.loading_gltfs { for gltf in &tracker.loading_gltfs {
info!( debug!(
"checking for loaded gltfs {:?}", "checking for loaded gltfs {:?}",
asset_server.get_load_state(gltf) asset_server.get_load_state(gltf)
); );
if asset_server.get_load_state(gltf.clone()) == LoadState::Loaded { if let Some(load_state) = asset_server.get_load_state(gltf.clone()) {
debug!("Adding scene to processing list"); if load_state == LoadState::Loaded {
loaded_gltfs.push(gltf.clone()); debug!("Adding scene to processing list");
loaded_gltfs.push(gltf.clone());
}
} }
} }
@ -60,14 +66,7 @@ pub fn process_loaded_scenes(
for gltf_handle in &loaded_gltfs { for gltf_handle in &loaded_gltfs {
if let Some(gltf) = gltfs.get_mut(gltf_handle) { if let Some(gltf) = gltfs.get_mut(gltf_handle) {
// TODO this is a temporary workaround for library management gltf_extras_to_components(gltf, &mut scenes, &*type_registry);
if let Some(asset_path) = asset_server.get_handle_path(gltf_handle) {
let gltf_name = asset_path.path().file_stem().unwrap().to_str().unwrap();
gltf_extras_to_components(gltf, &mut scenes, &*type_registry, gltf_name);
//gltf_extras_to_prefab_infos(gltf, &mut scenes, &*type_registry, gltf_name);
} else {
gltf_extras_to_components(gltf, &mut scenes, &*type_registry, "");
}
} }
tracker.loading_gltfs.remove(gltf_handle); tracker.loading_gltfs.remove(gltf_handle);
tracker.loaded_gltfs.insert(gltf_handle.clone()); tracker.loaded_gltfs.insert(gltf_handle.clone());

View File

@ -6,7 +6,7 @@ use bevy_asset_loader::prelude::*;
#[derive(AssetCollection, Resource)] #[derive(AssetCollection, Resource)]
pub struct GameAssets { pub struct GameAssets {
#[asset(key = "world")] #[asset(key = "world")]
pub world: Handle<Scene>, pub world: Handle<Gltf>,
#[asset(key = "models", collection(typed, mapped))] #[asset(key = "models", collection(typed, mapped))]
pub models: HashMap<String, Handle<Gltf>>, pub models: HashMap<String, Handle<Gltf>>,

View File

@ -1,5 +1,4 @@
pub mod camera; pub mod camera;
use bevy_rapier3d::prelude::Velocity;
pub use camera::*; pub use camera::*;
pub mod lighting; pub mod lighting;
@ -11,61 +10,12 @@ pub use relationships::*;
pub mod physics; pub mod physics;
pub use physics::*; pub use physics::*;
// pub mod blueprints; // pub mod save_load;
// pub use blueprints::*; // pub use save_load::*;
pub mod save_load;
pub use save_load::*;
use bevy::prelude::*; use bevy::prelude::*;
use bevy_gltf_blueprints::*; use bevy_gltf_blueprints::*;
use rand::Rng;
fn spawn_test(
keycode: Res<Input<KeyCode>>,
mut commands: Commands,
mut game_world: Query<(Entity, &Children), With<GameWorldTag>>,
) {
if keycode.just_pressed(KeyCode::T) {
let world = game_world.single_mut();
let world = world.1[0];
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()),
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)),
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);
}
}
pub struct CorePlugin; pub struct CorePlugin;
impl Plugin for CorePlugin { impl Plugin for CorePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
@ -73,12 +23,10 @@ impl Plugin for CorePlugin {
LightingPlugin, LightingPlugin,
CameraPlugin, CameraPlugin,
PhysicsPlugin, PhysicsPlugin,
SaveLoadPlugin, // SaveLoadPlugin,
BlueprintsPlugin { BlueprintsPlugin {
library_folder: "advanced/models/library".into(), library_folder: "advanced/models/library".into(),
}, },
)) ));
// just for testing
.add_systems(Update, spawn_test);
} }
} }

View File

@ -4,11 +4,15 @@ use crate::{
assets::GameAssets, assets::GameAssets,
state::{GameState, InAppRunning}, state::{GameState, InAppRunning},
}; };
use bevy_gltf_blueprints::GameWorldTag; use bevy_gltf_blueprints::{BluePrintBundle, BlueprintName, GameWorldTag};
use bevy_rapier3d::prelude::Velocity;
use rand::Rng;
pub fn setup_game( pub fn setup_game(
mut commands: Commands, mut commands: Commands,
game_assets: Res<GameAssets>, game_assets: Res<GameAssets>,
models: Res<Assets<bevy::gltf::Gltf>>,
mut next_game_state: ResMut<NextState<GameState>>, mut next_game_state: ResMut<NextState<GameState>>,
) { ) {
println!("setting up all stuff"); println!("setting up all stuff");
@ -20,7 +24,12 @@ pub fn setup_game(
commands.spawn(( commands.spawn((
SceneBundle { SceneBundle {
scene: game_assets.world.clone(), // note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene
scene: models
.get(game_assets.world.id())
.expect("main level should have been loaded")
.scenes[0]
.clone(),
..default() ..default()
}, },
bevy::prelude::Name::from("world"), bevy::prelude::Name::from("world"),
@ -30,3 +39,47 @@ pub fn setup_game(
next_game_state.set(GameState::InGame) next_game_state.set(GameState::InGame)
} }
pub fn spawn_test(
keycode: Res<Input<KeyCode>>,
mut commands: Commands,
mut game_world: Query<(Entity, &Children), With<GameWorldTag>>,
) {
if keycode.just_pressed(KeyCode::T) {
let world = game_world.single_mut();
let world = world.1[0];
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()),
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)),
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);
}
}

View File

@ -1,9 +1,6 @@
use bevy::prelude::*; use bevy::prelude::*;
use crate::{ use crate::state::{AppState, GameState, InMainMenu};
core::save_load::{LoadRequest, SaveRequest},
state::{AppState, GameState, InMainMenu},
};
pub fn setup_main_menu(mut commands: Commands) { pub fn setup_main_menu(mut commands: Commands) {
commands.spawn((Camera2dBundle::default(), InMainMenu)); commands.spawn((Camera2dBundle::default(), InMainMenu));
@ -97,8 +94,8 @@ pub fn main_menu(
mut next_app_state: ResMut<NextState<AppState>>, mut next_app_state: ResMut<NextState<AppState>>,
// mut next_game_state: ResMut<NextState<GameState>>, // mut next_game_state: ResMut<NextState<GameState>>,
mut save_requested_events: EventWriter<SaveRequest>, // mut save_requested_events: EventWriter<SaveRequest>,
mut load_requested_events: EventWriter<LoadRequest>, // mut load_requested_events: EventWriter<LoadRequest>,
) { ) {
if keycode.just_pressed(KeyCode::Return) { if keycode.just_pressed(KeyCode::Return) {
next_app_state.set(AppState::AppLoading); next_app_state.set(AppState::AppLoading);

View File

@ -70,7 +70,7 @@ pub fn test_collision_events(
mut collision_events: EventReader<CollisionEvent>, mut collision_events: EventReader<CollisionEvent>,
mut contact_force_events: EventReader<ContactForceEvent>, mut contact_force_events: EventReader<ContactForceEvent>,
) { ) {
for collision_event in collision_events.iter() { for collision_event in collision_events.read() {
println!("collision"); println!("collision");
match collision_event { match collision_event {
CollisionEvent::Started(_entity1, _entity2, _) => { CollisionEvent::Started(_entity1, _entity2, _) => {
@ -82,7 +82,7 @@ pub fn test_collision_events(
} }
} }
for contact_force_event in contact_force_events.iter() { for contact_force_event in contact_force_events.read() {
println!("Received contact force event: {:?}", contact_force_event); println!("Received contact force event: {:?}", contact_force_event);
} }
} }
@ -102,7 +102,8 @@ impl Plugin for GamePlugin {
( (
// insert_dependant_component::<Player, ShouldBeWithPlayer>, // insert_dependant_component::<Player, ShouldBeWithPlayer>,
player_move_demo, //.run_if(in_state(AppState::Running)), player_move_demo, //.run_if(in_state(AppState::Running)),
// test_collision_events // test_collision_events
spawn_test,
) )
.run_if(in_state(GameState::InGame)), .run_if(in_state(GameState::InGame)),
) )

View File

@ -1,5 +1,6 @@
use super::Player; use super::Player;
use bevy::prelude::*; use bevy::prelude::*;
use bevy_gltf_blueprints::GltfBlueprintsSet;
#[derive(Component, Reflect, Default, Debug)] #[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)] #[reflect(Component)]
@ -27,11 +28,7 @@ pub fn picking(
pub struct PickingPlugin; pub struct PickingPlugin;
impl Plugin for PickingPlugin { impl Plugin for PickingPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.register_type::<Pickable>().add_systems( app.register_type::<Pickable>()
Update, .add_systems(Update, (picking.after(GltfBlueprintsSet::AfterSpawn),));
(
picking, //.run_if(in_state(AppState::Running)),
),
);
} }
} }

View File

@ -1,4 +1,4 @@
use bevy::{asset::ChangeWatcher, prelude::*}; use bevy::prelude::*;
use bevy_editor_pls::prelude::*; use bevy_editor_pls::prelude::*;
mod core; mod core;
@ -19,13 +19,7 @@ use test_components::*;
fn main() { fn main() {
App::new() App::new()
.add_plugins(( .add_plugins((
DefaultPlugins.set(AssetPlugin { DefaultPlugins.set(AssetPlugin::default()),
// This tells the AssetServer to watch for changes to assets.
// It enables our scenes to automatically reload in game when we modify their files.
// practical in our case to be able to edit the shaders without needing to recompile
// watch_for_changes: ChangeWatcher::with_delay(Duration::from_millis(50)), : FIXME: breaks scene save/loading
..default()
}),
// editor // editor
EditorPlugin::default(), EditorPlugin::default(),
// our custom plugins // our custom plugins

View File

@ -6,7 +6,7 @@ use bevy_asset_loader::prelude::*;
#[derive(AssetCollection, Resource)] #[derive(AssetCollection, Resource)]
pub struct GameAssets { pub struct GameAssets {
#[asset(key = "world")] #[asset(key = "world")]
pub world: Handle<Scene>, pub world: Handle<Gltf>,
#[asset(key = "models", collection(typed, mapped))] #[asset(key = "models", collection(typed, mapped))]
pub models: HashMap<String, Handle<Gltf>>, pub models: HashMap<String, Handle<Gltf>>,

View File

@ -1,5 +1,4 @@
pub mod camera; pub mod camera;
use bevy_rapier3d::prelude::Velocity;
pub use camera::*; pub use camera::*;
pub mod lighting; pub mod lighting;

View File

@ -17,9 +17,10 @@ use super::{Fox, Player, Robot};
pub fn setup_game( pub fn setup_game(
mut commands: Commands, mut commands: Commands,
game_assets: Res<GameAssets>, game_assets: Res<GameAssets>,
models: Res<Assets<bevy::gltf::Gltf>>,
mut next_game_state: ResMut<NextState<GameState>>, mut next_game_state: ResMut<NextState<GameState>>,
) { ) {
println!("setting up all stuff");
commands.insert_resource(AmbientLight { commands.insert_resource(AmbientLight {
color: Color::WHITE, color: Color::WHITE,
brightness: 0.2, brightness: 0.2,
@ -28,7 +29,12 @@ pub fn setup_game(
commands.spawn(( commands.spawn((
SceneBundle { SceneBundle {
scene: game_assets.world.clone(), // note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene
scene: models
.get(game_assets.world.id())
.expect("main level should have been loaded")
.scenes[0]
.clone(),
..default() ..default()
}, },
bevy::prelude::Name::from("world"), bevy::prelude::Name::from("world"),

View File

@ -1,4 +1,4 @@
use bevy::{asset::ChangeWatcher, prelude::*}; use bevy::prelude::*;
use bevy_editor_pls::prelude::*; use bevy_editor_pls::prelude::*;
mod core; mod core;
@ -19,13 +19,7 @@ use test_components::*;
fn main() { fn main() {
App::new() App::new()
.add_plugins(( .add_plugins((
DefaultPlugins.set(AssetPlugin { DefaultPlugins.set(AssetPlugin::default()),
// This tells the AssetServer to watch for changes to assets.
// It enables our scenes to automatically reload in game when we modify their files.
// practical in our case to be able to edit the shaders without needing to recompile
// watch_for_changes: ChangeWatcher::with_delay(Duration::from_millis(50)), : FIXME: breaks scene save/loading
..default()
}),
// editor // editor
EditorPlugin::default(), EditorPlugin::default(),
// our custom plugins // our custom plugins

View File

@ -63,7 +63,7 @@ pub fn test_collision_events(
mut collision_events: EventReader<CollisionEvent>, mut collision_events: EventReader<CollisionEvent>,
mut contact_force_events: EventReader<ContactForceEvent>, mut contact_force_events: EventReader<ContactForceEvent>,
) { ) {
for collision_event in collision_events.iter() { for collision_event in collision_events.read() {
println!("collision"); println!("collision");
match collision_event { match collision_event {
CollisionEvent::Started(_entity1, _entity2, _) => { CollisionEvent::Started(_entity1, _entity2, _) => {
@ -75,7 +75,7 @@ pub fn test_collision_events(
} }
} }
for contact_force_event in contact_force_events.iter() { for contact_force_event in contact_force_events.read() {
println!("Received contact force event: {:?}", contact_force_event); println!("Received contact force event: {:?}", contact_force_event);
} }
} }

View File

@ -1,8 +1,7 @@
use bevy::{asset::ChangeWatcher, gltf::Gltf, prelude::*}; use bevy::{gltf::Gltf, prelude::*};
use bevy_editor_pls::prelude::*; use bevy_editor_pls::prelude::*;
use bevy_gltf_components::ComponentsFromGltfPlugin; use bevy_gltf_components::ComponentsFromGltfPlugin;
use bevy_rapier3d::prelude::*; use bevy_rapier3d::prelude::*;
use std::time::Duration;
mod core; mod core;
use crate::core::*; use crate::core::*;
@ -28,13 +27,7 @@ enum AppState {
fn main() { fn main() {
App::new() App::new()
.add_plugins(( .add_plugins((
DefaultPlugins.set(AssetPlugin { DefaultPlugins.set(AssetPlugin::default()),
// This tells the AssetServer to watch for changes to assets.
// It enables our scenes to automatically reload in game when we modify their files.
// practical in our case to be able to edit the shaders without needing to recompile
watch_for_changes: ChangeWatcher::with_delay(Duration::from_millis(50)),
..default()
}),
// editor // editor
EditorPlugin::default(), EditorPlugin::default(),
// physics // physics
@ -53,33 +46,33 @@ fn main() {
} }
#[derive(Resource)] #[derive(Resource)]
struct AssetLoadHelper(Handle<Scene>); pub struct MyGltf(pub Handle<Gltf>);
// we preload the data here, but this is for DEMO PURPOSES ONLY !! Please use https://github.com/NiklasEi/bevy_asset_loader or a similar logic to seperate loading / pre processing // we preload the data here, but this is for DEMO PURPOSES ONLY !! Please use https://github.com/NiklasEi/bevy_asset_loader or a similar logic to seperate loading / pre processing
// of assets from the spawning // of assets from the spawning
// AssetLoadHelper is also just for the same purpose, you do not need it in a real scenario // MyGltf is also just for the same purpose, you do not need it in a real scenario
// the states here are also for demo purposes only, // the states here are also for demo purposes only,
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
let tmp: Handle<Scene> = asset_server.load("basic/models/level1.glb#Scene0"); commands.insert_resource(MyGltf(asset_server.load("basic/models/level1.glb")));
commands.insert_resource(AssetLoadHelper(tmp));
} }
fn spawn_level( fn spawn_level(
mut commands: Commands, mut commands: Commands,
scene_markers: Query<&LoadedMarker>, scene_markers: Query<&LoadedMarker>,
preloaded_scene: Res<AssetLoadHelper>,
mut asset_event_reader: EventReader<AssetEvent<Gltf>>, mut asset_event_reader: EventReader<AssetEvent<Gltf>>,
mut next_state: ResMut<NextState<AppState>>, mut next_state: ResMut<NextState<AppState>>,
models: Res<Assets<bevy::gltf::Gltf>>,
) { ) {
if let Some(asset_event) = asset_event_reader.iter().next() { if let Some(asset_event) = asset_event_reader.read().next() {
match asset_event { match asset_event {
AssetEvent::Created { handle: _ } => { AssetEvent::Added { id } => {
info!("GLTF loaded"); info!("GLTF loaded/ added {:?}", asset_event);
let my_gltf = models.get(*id).unwrap();
if scene_markers.is_empty() { if scene_markers.is_empty() {
info!("spawning scene"); info!("spawning scene");
commands.spawn(( commands.spawn((
SceneBundle { SceneBundle {
scene: preloaded_scene.0.clone(), scene: my_gltf.scenes[0].clone(),
..default() ..default()
}, },
LoadedMarker, LoadedMarker,