Compare commits

...

7 Commits

Author SHA1 Message Date
kaosat.dev b756819088 chore(Blenvy): more general cleanups & doc updates
* also more work done on example upgrades
2024-07-16 23:51:49 +02:00
kaosat.dev 30b052d4d2 feat(Blenvy:Bevy):
* lots of doc updates
 * removed obsolete code
 * some cleanups
 * started adding & upgrading pieces of save_load into blenvy
2024-07-16 23:39:09 +02:00
kaosat.dev 0938a1d10c refactor(Blenvy:Bevy): more examples restructure & cleanup 2024-07-16 11:17:05 +02:00
kaosat.dev 7acb1f3a57 refactor(examples): started simplifying & restructuring examples 2024-07-16 11:11:25 +02:00
kaosat.dev b25fc56ea6 refactor(Blenvy:Bevy):
* removed "Library" component & co
* "InBlueprint" components are now always inserted: removed "NoInblueprint" component
2024-07-16 11:10:53 +02:00
kaosat.dev c1e2bb6ecf fix(Blenvy:Blender): fixed issues with delete operator for "custom property" style components 2024-07-16 11:03:11 +02:00
kaosat.dev e394edade2 chore(Blenvy:Bevy): clippy fixes 2024-07-16 08:26:08 +02:00
204 changed files with 28776 additions and 3581 deletions

View File

@ -1,7 +1,6 @@
[workspace]
members = [
"crates/*",
"examples/common*",
"examples/blenvy/*",
"testing/bevy_example/",
]

View File

@ -1,6 +1,6 @@
# Blender add-ons
- gltf_auto_export and bevy_components have been replaced with a single add-on for simplicity
- gltf_auto_export and bevy_components have been replaced with a single Blenvy add-on for simplicity
## Components:
@ -111,7 +111,7 @@ Blenvy will take care of loading all needed blueprints & other assets for you
```
## BlueprintDisabled
## BlueprintInstanceDisabled
you can now query for this component

13
TODO.md
View File

@ -58,7 +58,7 @@ Components:
- [x] BLENVY_OT_component_rename_component
- [x] BLENVY_OT_component_fix
- [x] add handling for core::ops::Range<f32> & other ranges
- [x] fix is_component_valid that is used in gltf_auto_export
- [x] fix is_component_valid that is used in blenvy
- [x] Hashmap Support
- [x] fix parsing of keys's type either on Bevy side (prefered) or on the Blender side
- [x] fix weird issue with missing "0" property when adding new entry in empty hashmap => happens only if the values for the "setter" have never been set
@ -286,7 +286,7 @@ Bevy Side:
- [ ] invalidate despawned entity & parent entities AABB
- [ ] add unloading/cache removal of materials
- [ ] add back and upgrade save-load
- [x] review & change general component insertion & spawning ordering & logic
- GltfComponentsSet::Injection => GltfBlueprintsSet::Spawn => GltfBlueprintsSet::AfterSpawn
@ -299,8 +299,13 @@ Bevy Side:
- [x] how to deal with animation graphs ?
- [ ] remove "Library" component & co
- [ ] BlueprintDisabled => BlueprintInstanceDisabled
- [x] remove "Library" component & co
- [x] make "InBlueprint" non optional,
- [ ] and perhaps rename it to "FromBlueprint(BlueprintInfo)"
- [x] BlueprintInstanceDisabled => BlueprintInstanceDisabled
- [x] fix "remove component" operator from the rename/fix/update components panel
- [ ] replace string in BlueprintInfo path with PathBuf ?
- [ ] update main docs
- [ ] rename project to Blenvy

View File

@ -3,8 +3,8 @@ name = "blenvy"
version = "0.1.0"
authors = ["Mark 'kaosat-dev' Moissette"]
description = "Allows you to define Bevy components direclty inside gltf files and instanciate the components on the Bevy side."
homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"
repository = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"
homepage = "https://github.com/kaosat-dev/Blenvy"
repository = "https://github.com/kaosat-dev/Blenvy"
keywords = ["gamedev", "bevy", "assets", "gltf", "components"]
categories = ["game-development"]
edition = "2021"

View File

@ -1,23 +1,39 @@
[![Crates.io](https://img.shields.io/crates/v/blenvy)](https://crates.io/crates/blenvy)
[![Docs](https://img.shields.io/docsrs/blenvy)](https://docs.rs/blenvy/latest/blenvy/)
[![License](https://img.shields.io/crates/l/blenvy)](https://github.com/kaosat-dev/Blender_bevy_components_workflow/blob/main/crates/blenvy/License.md)
[![License](https://img.shields.io/crates/l/blenvy)](https://github.com/kaosat-dev/Blenvy/blob/main/crates/blenvy/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)
# blenvy
this crate adds the ability to define Blueprints/Prefabs for [Bevy](https://bevyengine.org/) inside gltf files and spawn them in Bevy.
This crate allows you to
- define [Bevy](https://bevyengine.org/) components direclty inside gltf files and instanciate the components on the Bevy side.
- define Blueprints/Prefabs for [Bevy](https://bevyengine.org/) inside gltf files and spawn them in Bevy.
* Allows you to create lightweight levels, where all assets are different gltf files and loaded after the main level is loaded
* Allows you to spawn different entities from gtlf files at runtime in a clean manner, including simplified animation support !
* Allows you to create lightweight levels, where all assets are different gltf files and loaded after the main level is loaded
* Allows you to spawn different entities from gtlf files at runtime in a clean manner, including simplified animation support !
A blueprint is a set of **overrideable** components + a hierarchy: ie
A blueprint is a set of **overrideable** components + a hierarchy: ie
* just a Gltf file with Gltf_extras specifying components
* a component called BlueprintInfo
* just a Gltf file with Gltf_extras specifying components
* a component called BlueprintInfo
Particularly useful when using [Blender](https://www.blender.org/) as an editor for the [Bevy](https://bevyengine.org/) game engine, combined with the Blender add-on that do a lot of the work for you
- [blenvy](https://github.com/kaosat-dev/Blenvy/tree/main/tools/blenvy)
- allows you to create a Json export of all your components/ registered types.
Its main use case is as a backbone for the [```blenvy``` Blender add-on](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/tools/blenvy), that allows you to add & edit components directly in Blender, using the actual type definitions from Bevy
(and any of your custom types & components that you register in Bevy).
- adds the ability to easilly **save** and **load** your game worlds for [Bevy](https://bevyengine.org/) .
Particularly useful when using [Blender](https://www.blender.org/) as an editor for the [Bevy](https://bevyengine.org/) game engine, combined with the Blender add-on that do a lot of the work for you
- [blenvy](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/tools/blenvy)
* 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/blenvy) that does a lot of the work for you (including spliting generating seperate gltf files for your static vs dynamic assets)
## Usage
@ -40,12 +56,27 @@ fn main() {
.add_plugins(DefaultPlugins)
.add_plugins(BlenvyPlugin)
.add_systems(Startup, setup_game)
.add_systems(Update, spawn_blueprint_instance)
.run();
}
// not shown here: any other setup that is not specific to blueprints
fn spawn_blueprint(
// this is how you setup & spawn a level from a blueprint
fn setup_game(
mut commands: Commands,
) {
// here we spawn our game world/level, which is also a blueprint !
commands.spawn((
BlueprintInfo::from_path("levels/World.glb"), // all we need is a Blueprint info...
SpawnBlueprint, // and spawnblueprint to tell blenvy to spawn the blueprint now
HideUntilReady, // only reveal the level once it is ready
GameWorldTag,
));
}
fn spawn_blueprint_instance(
mut commands: Commands,
keycode: Res<Input<KeyCode>>,
){
@ -152,7 +183,7 @@ any component you specify when spawning the Blueprint that is also specified **w
for example
```rust no_run
commands.spawn((
BlueprintInfo("Health_Pickup".to_string()),
BlueprintInfo(path: "Health_Pickup.glb".into()),
SpawnBlueprint,
TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)),
HealthPowerUp(20)// if this is component is also present inside the "Health_Pickup" blueprint, that one will be replaced with this component during spawning
@ -168,22 +199,7 @@ There is also a ```BluePrintBundle``` for convenience , which just has
## 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: BlueprintInfo("TestBlueprint".to_string()),
..Default::default()
},
Library("models".into()) // now the path to the blueprint above will be /assets/models/TestBlueprint.glb
))
```
- When a blueprint is spawned, an ```InBlueprint``` component is inserted into all its children entities (and nested children etc)
- 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"
@ -192,16 +208,10 @@ This way all your levels, your dynamic entities etc, are kept seperated from UI
> 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"),
commands.spawn((
BlueprintInfo::from_path("levels/World.glb"),
SpawnBlueprint,
HideUntilReady,
GameWorldTag, // here it is
));
```
@ -219,7 +229,7 @@ Typically , the order of systems should be
***bevy_gltf_components (GltfComponentsSet::Injection)*** => ***blenvy (GltfBlueprintsSet::Spawn, GltfBlueprintsSet::AfterSpawn)*** => ***replace_proxies***
see https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/blenvy/basic for how to set it up correctly
see https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/basic for how to set it up correctly
@ -273,44 +283,32 @@ pub fn animation_change_on_proximity_foxes(
}
```
see https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/blenvy/animation for how to set it up correctly
see https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/animation for how to set it up correctly
particularly from https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/blenvy/animation/game/in_game.rs
particularly from https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/animation/game/in_game.rs
## Materials
You have the option of using "material libraries" to share common textures/materials between blueprints, in order to avoid asset & memory bloat:
Ff you enable it on the blender side, Blenvy will be using "material libraries" to share common textures/materials between blueprints, in order to avoid asset & memory bloat:
Ie for example without this option, 56 different blueprints using the same material with a large texture would lead to the material/texture being embeded
56 times !!
you can configure this with the settings:
```rust
material_library: true // defaults to false, enable this to enable automatic injection of materials from material library files
```
> Important! you must take care of preloading your material librairy gltf files in advance, using for example ```bevy_asset_loader```since
```blenvy``` currently does NOT take care of loading those at runtime
see https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/blenvy/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)
Generating optimised blueprints and material libraries can be automated using the latests version of the [Blender plugin](https://github.com/kaosat-dev/Blenvy/tree/main/tools/blenvy)
## Examples
https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/blenvy/basic
https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/components
https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/blenvy/basic_xpbd_physics
https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/blueprints
https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/blenvy/animation
https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/animation
https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/blenvy/materials
https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/save_load
https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/blenvy/multiple_levels_multiple_blendfiles
https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/demo (a full fledged demo)
## Compatible Bevy versions

View File

@ -1,190 +0,0 @@
use std::path::Path;
use bevy::{
asset::{AssetServer, Assets, Handle},
ecs::{
component::Component,
entity::Entity,
query::{Added, With},
reflect::ReflectComponent,
system::{Commands, Query, Res, ResMut},
},
gltf::Gltf,
hierarchy::{Children, Parent},
log::debug,
pbr::StandardMaterial,
reflect::Reflect,
render::mesh::Mesh,
};
use crate::{AssetLoadTracker, BlueprintAssetsLoadState, BlenvyConfig, BlueprintInstanceReady};
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// struct containing the name & path of the material to apply
pub struct MaterialInfo {
pub name: String,
pub path: String,
}
/// flag component
#[derive(Component)]
pub(crate) struct BlueprintMaterialAssetsLoaded;
/// flag component
#[derive(Component)]
pub(crate) struct BlueprintMaterialAssetsNotLoaded;
/// system that injects / replaces materials from material library
pub(crate) fn materials_inject(
blenvy_config: ResMut<BlenvyConfig>,
material_infos: Query<(Entity, &MaterialInfo), Added<MaterialInfo>>,
asset_server: Res<AssetServer>,
mut commands: Commands,
) {
for (entity, material_info) in material_infos.iter() {
println!("Entity with material info {:?} {:?}", entity, material_info);
let material_full_path = format!("{}#{}", material_info.path, material_info.name);
if blenvy_config
.materials_cache
.contains_key(&material_full_path)
{
debug!("material is cached, retrieving");
blenvy_config
.materials_cache
.get(&material_full_path)
.expect("we should have the material available");
commands
.entity(entity)
.insert(BlueprintMaterialAssetsLoaded);
} else {
let material_file_handle = asset_server.load_untyped(&material_info.path.clone()); // : Handle<Gltf>
let material_file_id = material_file_handle.id();
let asset_infos: Vec<AssetLoadTracker> = vec![AssetLoadTracker {
name: material_info.name.clone(),
path: material_info.path.clone(),
id: material_file_id,
loaded: false,
handle: material_file_handle.clone(),
}];
commands
.entity(entity)
.insert(BlueprintAssetsLoadState {
all_loaded: false,
asset_infos,
..Default::default()
})
.insert(BlueprintMaterialAssetsNotLoaded);
}
}
}
// TODO, merge with blueprints_check_assets_loading, make generic ?
pub(crate) fn check_for_material_loaded(
mut blueprint_assets_to_load: Query<
(Entity, &mut BlueprintAssetsLoadState),
With<BlueprintMaterialAssetsNotLoaded>,
>,
asset_server: Res<AssetServer>,
mut commands: Commands,
) {
for (entity, mut assets_to_load) in blueprint_assets_to_load.iter_mut() {
let mut all_loaded = true;
let mut loaded_amount = 0;
let total = assets_to_load.asset_infos.len();
for tracker in assets_to_load.asset_infos.iter_mut() {
let asset_id = tracker.id;
let loaded = asset_server.is_loaded_with_dependencies(asset_id);
tracker.loaded = loaded;
if loaded {
loaded_amount += 1;
} else {
all_loaded = false;
}
}
let progress: f32 = loaded_amount as f32 / total as f32;
assets_to_load.progress = progress;
if all_loaded {
assets_to_load.all_loaded = true;
commands
.entity(entity)
.insert(BlueprintMaterialAssetsLoaded)
.remove::<BlueprintMaterialAssetsNotLoaded>();
}
}
}
/// system that injects / replaces materials from material library
pub(crate) fn materials_inject2(
mut blenvy_config: ResMut<BlenvyConfig>,
material_infos: Query<
(&MaterialInfo, &Children),
(
Added<BlueprintMaterialAssetsLoaded>,
With<BlueprintMaterialAssetsLoaded>,
),
>,
with_materials_and_meshes: Query<
(),
(
With<Parent>,
With<Handle<StandardMaterial>>,
With<Handle<Mesh>>,
),
>,
assets_gltf: Res<Assets<Gltf>>,
asset_server: Res<AssetServer>,
mut commands: Commands,
) {
for (material_info, children) in material_infos.iter() {
let material_full_path = format!("{}#{}", material_info.path, material_info.name);
let mut material_found: Option<&Handle<StandardMaterial>> = None;
if blenvy_config
.materials_cache
.contains_key(&material_full_path)
{
debug!("material is cached, retrieving");
let material = blenvy_config
.materials_cache
.get(&material_full_path)
.expect("we should have the material available");
material_found = Some(material);
} else {
let model_handle: Handle<Gltf> = asset_server.load(material_info.path.clone()); // FIXME: kinda weird now
let mat_gltf = assets_gltf
.get(model_handle.id())
.expect("material should have been preloaded");
if mat_gltf.named_materials.contains_key(&material_info.name as &str) {
let material = mat_gltf
.named_materials
.get(&material_info.name as &str)
.expect("this material should have been loaded");
blenvy_config
.materials_cache
.insert(material_full_path, material.clone());
material_found = Some(material);
}
}
if let Some(material) = material_found {
for child in children.iter() {
if with_materials_and_meshes.contains(*child) {
debug!(
"injecting material {}, path: {:?}",
material_info.name,
material_info.path.clone()
);
commands.entity(*child).insert(material.clone());
}
}
}
}
}

View File

@ -1,248 +0,0 @@
/// helper component, for tracking loaded assets's loading state, id , handle etc
#[derive(Default, Debug)]
pub(crate) struct AssetLoadTracker<T: bevy::prelude::Asset> {
#[allow(dead_code)]
pub name: String,
pub id: AssetId<T>,
pub loaded: bool,
#[allow(dead_code)]
pub handle: Handle<T>,
}
/// helper component, for tracking loaded assets
#[derive(Component, Debug)]
pub(crate) struct AssetsToLoad<T: bevy::prelude::Asset> {
pub all_loaded: bool,
pub asset_infos: Vec<AssetLoadTracker<T>>,
pub progress: f32,
}
impl<T: bevy::prelude::Asset> Default for AssetsToLoad<T> {
fn default() -> Self {
Self {
all_loaded: Default::default(),
asset_infos: Default::default(),
progress: Default::default(),
}
}
}
/// flag component, usually added when a blueprint is loaded
#[derive(Component)]
pub(crate) struct BlueprintAssetsLoaded;
/// flag component
#[derive(Component)]
pub(crate) struct BlueprintAssetsNotLoaded;
/// spawning prepare function,
/// * also takes into account the already exisiting "override" components, ie "override components" > components from blueprint
pub(crate) fn prepare_blueprints(
spawn_placeholders: Query<
(
Entity,
&BlueprintName,
Option<&Parent>,
Option<&Library>,
Option<&Name>,
Option<&BlueprintsList>,
),
(Added<BlueprintName>, Added<SpawnBlueprint>, Without<Spawned>),
>,
mut commands: Commands,
asset_server: Res<AssetServer>,
blenvy_config: Res<BluePrintsConfig>,
) {
for (entity, blupeprint_name, original_parent, library_override, name, blueprints_list) in
spawn_placeholders.iter()
{
debug!(
"requesting to spawn {:?} for entity {:?}, id: {:?}, parent:{:?}",
blupeprint_name.0, name, entity, original_parent
);
// println!("main model path {:?}", model_path);
if blueprints_list.is_some() {
let blueprints_list = blueprints_list.unwrap();
// println!("blueprints list {:?}", blueprints_list.0.keys());
let mut asset_infos: Vec<AssetLoadTracker<Gltf>> = vec![];
let library_path =
library_override.map_or_else(|| &blenvy_config.library_folder, |l| &l.0);
for (blueprint_name, _) in blueprints_list.0.iter() {
let model_file_name = format!("{}.{}", &blueprint_name, &blenvy_config.format);
let model_path = Path::new(&library_path).join(Path::new(model_file_name.as_str()));
let model_handle: Handle<Gltf> = asset_server.load(model_path.clone());
let model_id = model_handle.id();
let loaded = asset_server.is_loaded_with_dependencies(model_id);
if !loaded {
asset_infos.push(AssetLoadTracker {
name: model_path.to_string_lossy().into(),
id: model_id,
loaded: false,
handle: model_handle.clone(),
});
}
}
// if not all assets are already loaded, inject a component to signal that we need them to be loaded
if !asset_infos.is_empty() {
commands
.entity(entity)
.insert(AssetsToLoad {
all_loaded: false,
asset_infos,
..Default::default()
})
.insert(BlueprintAssetsNotLoaded);
} else {
commands.entity(entity).insert(BlueprintAssetsLoaded);
}
} else {
// in case there are no blueprintsList, we revert back to the old behaviour
commands.entity(entity).insert(BlueprintAssetsLoaded);
}
}
}
pub(crate) fn blueprints_check_assets_loading(
mut blueprint_assets_to_load: Query<
(Entity, &mut AssetsToLoad<Gltf>),
With<BlueprintAssetsNotLoaded>,
>,
asset_server: Res<AssetServer>,
mut commands: Commands,
) {
for (entity, mut assets_to_load) in blueprint_assets_to_load.iter_mut() {
let mut all_loaded = true;
let mut loaded_amount = 0;
let total = assets_to_load.asset_infos.len();
for tracker in assets_to_load.asset_infos.iter_mut() {
let asset_id = tracker.id;
let loaded = asset_server.is_loaded_with_dependencies(asset_id);
tracker.loaded = loaded;
if loaded {
loaded_amount += 1;
} else {
all_loaded = false;
}
}
let progress: f32 = loaded_amount as f32 / total as f32;
// println!("progress: {}",progress);
assets_to_load.progress = progress;
if all_loaded {
assets_to_load.all_loaded = true;
commands
.entity(entity)
.insert(BlueprintAssetsLoaded)
.remove::<BlueprintAssetsNotLoaded>();
}
}
}
pub(crate) fn blueprints_spawn(
spawn_placeholders: Query<
(
Entity,
&BlueprintName,
Option<&Transform>,
Option<&Parent>,
Option<&Library>,
Option<&AddToGameWorld>,
Option<&Name>,
),
(
With<BlueprintAssetsLoaded>,
Added<BlueprintAssetsLoaded>,
Without<BlueprintAssetsNotLoaded>,
),
>,
mut commands: Commands,
mut game_world: Query<Entity, With<GameWorldTag>>,
assets_gltf: Res<Assets<Gltf>>,
asset_server: Res<AssetServer>,
blenvy_config: Res<BluePrintsConfig>,
children: Query<&Children>,
) {
for (
entity,
blupeprint_name,
transform,
original_parent,
library_override,
add_to_world,
name,
) in spawn_placeholders.iter()
{
debug!(
"attempting to spawn {:?} for entity {:?}, id: {:?}, parent:{:?}",
blupeprint_name.0, name, entity, original_parent
);
let what = &blupeprint_name.0;
let model_file_name = format!("{}.{}", &what, &blenvy_config.format);
// library path is either defined at the plugin level or overriden by optional Library components
let library_path =
library_override.map_or_else(|| &blenvy_config.library_folder, |l| &l.0);
let model_path = Path::new(&library_path).join(Path::new(model_file_name.as_str()));
// info!("attempting to spawn {:?}", model_path);
let model_handle: Handle<Gltf> = asset_server.load(model_path.clone()); // FIXME: kinda weird now
let gltf = assets_gltf.get(&model_handle).unwrap_or_else(|| {
panic!(
"gltf file {:?} should have been loaded",
model_path.to_str()
)
});
// 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
.named_scenes
.keys()
.next()
.expect("there should be at least one named scene in the gltf file to spawn");
let scene = &gltf.named_scenes[main_scene_name];
// transforms are optional, but still deal with them correctly
let mut transforms: Transform = Transform::default();
if transform.is_some() {
transforms = *transform.unwrap();
}
let mut original_children: Vec<Entity> = vec![];
if let Ok(c) = children.get(entity) {
for child in c.iter() {
original_children.push(*child);
}
}
commands.entity(entity).insert((
SceneBundle {
scene: scene.clone(),
transform: transforms,
..Default::default()
},
Spawned,
OriginalChildren(original_children),
BlueprintAnimations {
// these are animations specific to the inside of the blueprint
named_animations: gltf.named_animations.clone(),
},
));
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);
}
}
}

View File

@ -1,396 +0,0 @@
use std::path::{Path, PathBuf};
use bevy::{gltf::Gltf, prelude::*, utils::hashbrown::HashMap};
use serde_json::Value;
use crate::{BlueprintAssets, AssetsToLoad, AssetLoadTracker, BlenvyConfig, BlueprintAnimations, BlueprintAssetsLoaded, BlueprintAssetsNotLoaded};
/// this is a flag component for our levels/game world
#[derive(Component)]
pub struct GameWorldTag;
/// Main component for the blueprints
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct BlueprintName(pub String);
/// path component for the blueprints
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct BlueprintPath(pub String);
/// flag component needed to signify the intent to spawn a Blueprint
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct SpawnBlueprint;
#[derive(Component)]
/// flag component for dynamically spawned scenes
pub struct Spawned;
#[derive(Component, Debug)]
/// flag component added when a Blueprint instance ist Ready : ie :
/// - its assets have loaded
/// - it has finished spawning
pub struct BlueprintInstanceReady;
#[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 spawned 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)]
/// helper component, just to transfer child data
pub(crate) struct OriginalChildren(pub Vec<Entity>);
#[derive(Event, Debug)]
pub enum BlueprintEvent {
/// event fired when a blueprint has finished loading its assets & before it attempts spawning
AssetsLoaded {
blueprint_name: String,
blueprint_path: String,
// TODO: add assets list ?
},
/// event fired when a blueprint is COMPLETELY done spawning ie
/// - all its assets have been loaded
/// - the spawning attempt has been sucessfull
Spawned {
blueprint_name: String,
blueprint_path: String,
},
///
Ready {
blueprint_path: String,
}
}
use gltf::Gltf as RawGltf;
pub(crate) fn blueprints_prepare_spawn(
spawn_placeholders: Query<
(
Entity,
&BlueprintPath,
),
(Added<BlueprintPath>, Without<Spawned>, Without<SpawnBlueprint>)>,
// before 0.14 we have to use a seperate query, after migrating we can query at the root level
entities_with_assets: Query<
(
Entity,
/*&BlueprintName,
&BlueprintPath,
Option<&Parent>,*/
Option<&Name>,
Option<&BlueprintAssets>,
),
(Added<BlueprintAssets>), // Added<BlueprintAssets>
>,
bla_bla : Query<
(
Entity,
&BlueprintName,
&BlueprintPath,
Option<&Parent>,
Option<&BlueprintAssets>,
),(Added<BlueprintPath>)
>,
mut commands: Commands,
asset_server: Res<AssetServer>,
) {
for (entity, blueprint_path) in spawn_placeholders.iter() {
println!("added blueprint_path {:?}", blueprint_path);
/*commands.entity(entity).insert(
SceneBundle {
scene: asset_server.load(format!("{}#Scene0", &blueprint_path.0)), // "levels/World.glb#Scene0"),
..default()
},
);*/
// let model_handle: Handle<Gltf> = asset_server.load(model_path.clone());
}
for (entity, blueprint_name, blueprint_path, parent, all_assets) in bla_bla.iter() {
println!("added blueprint to spawn {:?} {:?}", blueprint_name, blueprint_path);
println!("all assets {:?}", all_assets);
/* prefetch attempt */
let gltf = RawGltf::open(format!("assets/{}", blueprint_path.0)).unwrap();// RawGltf::open("examples/Box.gltf")?;
for scene in gltf.scenes() {
let foo_extras = scene.extras().clone().unwrap();
let lookup: HashMap<String, Value> = serde_json::from_str(&foo_extras.get()).unwrap();
for (key, value) in lookup.clone().into_iter() {
println!("{} / {}", key, value);
}
if lookup.contains_key("BlueprintAssets"){
let assets_raw = &lookup["BlueprintAssets"];
println!("ASSETS RAW {}", assets_raw);
let x: BlueprintAssets = ron::from_str(&assets_raw.as_str().unwrap()).unwrap();
println!("YAHA {:?}", x);
}
//println!("SCENE EXTRAS {:?}", foo_extras);
}
//////////////
let untyped_handle = asset_server.load_untyped(&blueprint_path.0);
let asset_id = untyped_handle.id();
let loaded = asset_server.is_loaded_with_dependencies(asset_id);
let mut asset_infos: Vec<AssetLoadTracker> = vec![];
if !loaded {
asset_infos.push(AssetLoadTracker {
name: blueprint_name.0.clone(),
id: asset_id,
loaded: false,
handle: untyped_handle.clone(),
});
}
// now insert load tracker
if !asset_infos.is_empty() {
commands
.entity(entity)
.insert(AssetsToLoad {
all_loaded: false,
asset_infos,
..Default::default()
})
.insert(BlueprintAssetsNotLoaded);
} else {
commands.entity(entity).insert(BlueprintAssetsLoaded);
}
}
for (child_entity, child_entity_name, all_assets) in entities_with_assets.iter(){
println!("added assets {:?} to {:?}", all_assets, child_entity_name);
if all_assets.is_some() {
let mut asset_infos: Vec<AssetLoadTracker> = vec![];
for asset in all_assets.unwrap().0.iter() {
let untyped_handle = asset_server.load_untyped(&asset.path);
//println!("untyped handle {:?}", untyped_handle);
//asset_server.load(asset.path);
let asset_id = untyped_handle.id();
//println!("ID {:?}", asset_id);
let loaded = asset_server.is_loaded_with_dependencies(asset_id);
//println!("Loaded ? {:?}", loaded);
if !loaded {
asset_infos.push(AssetLoadTracker {
name: asset.name.clone(),
id: asset_id,
loaded: false,
handle: untyped_handle.clone(),
});
}
}
// now insert load tracker
if !asset_infos.is_empty() {
commands
.entity(child_entity)
.insert(AssetsToLoad {
all_loaded: false,
asset_infos,
..Default::default()
})
.insert(BlueprintAssetsNotLoaded);
} else {
commands.entity(child_entity).insert(BlueprintAssetsLoaded);
}
}
}
}
pub(crate) fn blueprints_check_assets_loading(
mut blueprint_assets_to_load: Query<
(Entity, Option<&Name>, &BlueprintPath, &mut AssetsToLoad),
With<BlueprintAssetsNotLoaded>,
>,
asset_server: Res<AssetServer>,
mut commands: Commands,
mut blueprint_events: EventWriter<BlueprintEvent>,
) {
for (entity, entity_name, blueprint_path, mut assets_to_load) in blueprint_assets_to_load.iter_mut() {
let mut all_loaded = true;
let mut loaded_amount = 0;
let total = assets_to_load.asset_infos.len();
for tracker in assets_to_load.asset_infos.iter_mut() {
let asset_id = tracker.id;
let loaded = asset_server.is_loaded_with_dependencies(asset_id);
println!("loading {}: // load state: {:?}", tracker.name, asset_server.load_state(asset_id));
// FIXME: hack for now
let mut failed = false;// asset_server.load_state(asset_id) == bevy::asset::LoadState::Failed(_error);
match asset_server.load_state(asset_id) {
bevy::asset::LoadState::Failed(_) => {
failed = true
},
_ => {}
}
tracker.loaded = loaded || failed;
if loaded || failed {
loaded_amount += 1;
} else {
all_loaded = false;
}
}
let progress: f32 = loaded_amount as f32 / total as f32;
println!("progress: {}",progress);
assets_to_load.progress = progress;
if all_loaded {
assets_to_load.all_loaded = true;
println!("done with loading {:?}, inserting components", entity_name);
blueprint_events.send(BlueprintEvent::AssetsLoaded {blueprint_name:"".into(), blueprint_path: blueprint_path.0.clone() });
commands
.entity(entity)
.insert(BlueprintAssetsLoaded)
.remove::<BlueprintAssetsNotLoaded>()
.remove::<AssetsToLoad>()
;
}
}
}
pub(crate) fn blueprints_spawn(
spawn_placeholders: Query<
(
Entity,
&BlueprintName,
&BlueprintPath,
Option<&Transform>,
Option<&Parent>,
Option<&AddToGameWorld>,
Option<&Name>,
),
(
With<BlueprintAssetsLoaded>,
Added<BlueprintAssetsLoaded>,
Without<BlueprintAssetsNotLoaded>,
),
>,
mut commands: Commands,
mut game_world: Query<Entity, With<GameWorldTag>>,
assets_gltf: Res<Assets<Gltf>>,
asset_server: Res<AssetServer>,
children: Query<&Children>,
) {
for (
entity,
blupeprint_name,
blueprint_path,
transform,
original_parent,
add_to_world,
name,
) in spawn_placeholders.iter()
{
info!(
"attempting to spawn blueprint {:?} for entity {:?}, id: {:?}, parent:{:?}",
blupeprint_name.0, name, entity, original_parent
);
// info!("attempting to spawn {:?}", model_path);
let model_handle: Handle<Gltf> = asset_server.load(blueprint_path.0.clone()); // FIXME: kinda weird now
let gltf = assets_gltf.get(&model_handle).unwrap_or_else(|| {
panic!(
"gltf file {:?} should have been loaded",
&blueprint_path.0
)
});
// 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
.named_scenes
.keys()
.next()
.expect("there should be at least one named scene in the gltf file to spawn");
let scene = &gltf.named_scenes[main_scene_name];
// transforms are optional, but still deal with them correctly
let mut transforms: Transform = Transform::default();
if transform.is_some() {
transforms = *transform.unwrap();
}
let mut original_children: Vec<Entity> = vec![];
if let Ok(c) = children.get(entity) {
for child in c.iter() {
original_children.push(*child);
}
}
let mut named_animations:HashMap<String, Handle<AnimationClip>> = HashMap::new() ;
for (key, value) in gltf.named_animations.iter() {
named_animations.insert(key.to_string(), value.clone());
}
commands.entity(entity).insert((
SceneBundle {
scene: scene.clone(),
transform: transforms,
..Default::default()
},
Spawned,
BlueprintInstanceReady, // FIXME: not sure if this is should be added here or in the post process
OriginalChildren(original_children),
BlueprintAnimations {
// these are animations specific to the inside of the blueprint
named_animations: named_animations//gltf.named_animations.clone(),
},
));
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);
}
}
}

View File

@ -1,165 +0,0 @@
use std::any::TypeId;
use bevy::gltf::Gltf;
use bevy::prelude::*;
use bevy::scene::SceneInstance;
// use bevy::utils::hashbrown::HashSet;
use crate::{BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintInfo, BlueprintReadyForPostProcess, BlueprintInstanceReady, BlueprintSpawning, SubBlueprintSpawnRoot, SubBlueprintsSpawnTracker};
use crate::{SpawnBlueprint, Spawned};
use crate::{
BlueprintEvent, CopyComponents, InBlueprint, NoInBlueprint, OriginalChildren
};
/// this system is in charge of doing any necessary post processing after a blueprint scene has been spawned
/// - it removes one level of useless nesting
/// - it copies the blueprint's root components to the entity it was spawned on (original entity)
/// - it copies the children of the blueprint scene into the original entity
/// - it add `AnimationLink` components so that animations can be controlled from the original entity
/// - it cleans up/ removes a few , by then uneeded components
pub(crate) fn spawned_blueprint_post_process( // rename to '
unprocessed_entities: Query<
(
Entity,
&Children,
&OriginalChildren,
&BlueprintAnimations,
Option<&NoInBlueprint>,
Option<&Name>,
&BlueprintInfo,
// sub blueprint instances tracker
Option<&SubBlueprintSpawnRoot>
),
(With<SpawnBlueprint>, With<SceneInstance>, Added<BlueprintReadyForPostProcess>),
>,
added_animation_players: Query<(Entity, &Parent), Added<AnimationPlayer>>,
all_children: Query<&Children>,
mut trackers: Query<(Entity, &mut SubBlueprintsSpawnTracker, &BlueprintInfo)>,
mut commands: Commands,
mut blueprint_events: EventWriter<BlueprintEvent>,
) {
for (original, children, original_children, animations, no_inblueprint, name, blueprint_info, track_root) in
unprocessed_entities.iter()
{
info!("post processing blueprint for entity {:?}", name);
if children.len() == 0 {
warn!("timing issue ! no children found, please restart your bevy app (bug being investigated)");
continue;
}
// the root node is the first & normally only child inside a scene, it is the one that has all relevant components
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;
}
}
// 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); // we do this here in order to avoid doing it to normal children
}
}
// 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,
});
// 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);
}
}
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(BlueprintAnimationPlayerLink(added));
}
}
}
commands.entity(original).remove::<SpawnBlueprint>();
commands.entity(original).remove::<Spawned>();
// commands.entity(original).remove::<Handle<Scene>>(); // FIXME: if we delete the handle to the scene, things get despawned ! not what we want
//commands.entity(original).remove::<BlueprintAssetsLoadState>(); // also clear the sub assets tracker to free up handles, perhaps just freeing up the handles and leave the rest would be better ?
//commands.entity(original).remove::<BlueprintAssetsLoaded>();
commands.entity(root_entity).despawn_recursive(); // Remove the root entity that comes from the spawned-in scene
commands.entity(original).insert( Visibility::Visible
);
commands.entity(original)
.insert(BlueprintInstanceReady)
.remove::<BlueprintSpawning>()
//
.remove::<BlueprintReadyForPostProcess>()
;
if let Some(track_root) = track_root {
//println!("got some root");
if let Ok((s_entity, mut tracker, bp_info)) = trackers.get_mut(track_root.0) {
// println!("found the tracker, setting loaded for {}", entity);
tracker.sub_blueprint_instances.entry(original).or_insert(true);
tracker.sub_blueprint_instances.insert(original, true);
// TODO: ugh, my limited rust knowledge, this is bad code
let mut all_spawned = true;
for key in tracker.sub_blueprint_instances.keys() {
let val = tracker.sub_blueprint_instances[key];
println!("Key: {key}, Spawned {}", val);
}
for val in tracker.sub_blueprint_instances.values() {
println!("spawned {}", val);
if !val {
all_spawned = false;
break;
}
}
if all_spawned { // TODO: move this to an other system, or "notify" the tracked root entity of the fact that all its sub blueprints have been loaded
println!("ALLLLL SPAAAAWNED for {}", track_root.0);
// commands.entity(track_root.0).insert(bundle)
blueprint_events.send(BlueprintEvent::Spawned {entity: track_root.0, blueprint_name: bp_info.name.clone(), blueprint_path: bp_info.path.clone()});
}
}
}
if trackers.get(original).is_err() {
// if it has no sub blueprint instances
blueprint_events.send(BlueprintEvent::Spawned {entity: original, blueprint_name: blueprint_info.name.clone(), blueprint_path: blueprint_info.path.clone()});
}
debug!("DONE WITH POST PROCESS");
info!("done instanciating blueprint for entity {:?}", name);
}
}

View File

@ -45,7 +45,7 @@ pub struct AnimationInfo {
}
/// Stores information about animations, to make things a bit easier api wise:
/// these components are automatically inserted by `gltf_auto_export` on entities that have animations
/// these components are automatically inserted by the `blenvy` Blender add-on on entities that have animations
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct AnimationInfos {

View File

@ -1,5 +1,5 @@
use crate::{BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintChildrenReady, BlueprintInfo, BlueprintInstanceReady, BlueprintSpawning, InBlueprint, SpawnBlueprint, SubBlueprintsSpawnTracker};
use bevy::asset::{AssetEvent, UntypedAssetId};
use crate::{BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintInfo, BlueprintInstanceReady, BlueprintSpawning, SpawnBlueprint, SubBlueprintsSpawnTracker};
use bevy::asset::AssetEvent;
use bevy::prelude::*;
use bevy::scene::SceneInstance;
use bevy::utils::hashbrown::HashMap;
@ -75,7 +75,7 @@ pub(crate) fn react_to_asset_changes(
}
}
}
if ! retained_candidates.contains(&entity) {
if ! retained_candidates.contains(entity) {
retained_candidates.push(**entity);
}
}

View File

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

View File

@ -66,7 +66,7 @@ fn aabbs_enabled(blenvy_config: Res<BlenvyConfig>) -> bool {
fn hot_reload(watching_for_changes: Res<WatchingForChanges>) -> bool {
// println!("hot reload ? {}", watching_for_changes.0);
return watching_for_changes.0
watching_for_changes.0
}
trait BlenvyApp {

View File

@ -37,23 +37,11 @@ impl BlueprintInfo {
#[reflect(Component)]
pub struct SpawnBlueprint;
#[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
/// flag component marking any spwaned child of blueprints
pub struct InBlueprint;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// flag component preventing any spawned 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
@ -72,7 +60,7 @@ pub struct HideUntilReady;
#[derive(Component)]
/// marker component, gets added to all children of a currently spawning blueprint instance, can be usefull to avoid manipulating still in progress entities
pub struct BlueprintDisabled;
pub struct BlueprintInstanceDisabled;
#[derive(Event, Debug)]
pub enum BlueprintEvent {
@ -169,12 +157,12 @@ pub(crate) fn blueprints_prepare_spawn(
let gltf = RawGltf::open(format!("assets/{}", blueprint_info.path)).unwrap();
for scene in gltf.scenes() {
let scene_extras = scene.extras().clone().unwrap();
let lookup: HashMap<String, Value> = serde_json::from_str(&scene_extras.get()).unwrap();
let lookup: HashMap<String, Value> = serde_json::from_str(scene_extras.get()).unwrap();
if lookup.contains_key("BlueprintAssets") {
let assets_raw = &lookup["BlueprintAssets"];
//println!("ASSETS RAW {}", assets_raw);
let all_assets: BlueprintAssets =
ron::from_str(&assets_raw.as_str().unwrap()).unwrap();
ron::from_str(assets_raw.as_str().unwrap()).unwrap();
// println!("all_assets {:?}", all_assets);
for asset in all_assets.assets.iter() {
@ -228,7 +216,7 @@ pub(crate) fn blueprints_prepare_spawn(
.entity(entity)
.insert(bevy::prelude::Name::from(blueprint_info.name.clone()));
// add the blueprint spawning marker
commands.entity(entity).insert((BlueprintSpawning));
commands.entity(entity).insert(BlueprintSpawning);
}
}
@ -250,12 +238,8 @@ pub(crate) fn blueprints_check_assets_loading(
let asset_id = tracker.id;
let loaded = asset_server.is_loaded_with_dependencies(asset_id);
// FIXME: hack for now
let mut failed = false; // asset_server.load_state(asset_id) == bevy::asset::LoadState::Failed(_error);
match asset_server.load_state(asset_id) {
bevy::asset::LoadState::Failed(_) => failed = true,
_ => {}
}
let mut failed = false;
if let bevy::asset::LoadState::Failed(_) = asset_server.load_state(asset_id) { failed = true }
tracker.loaded = loaded || failed;
if loaded || failed {
loaded_amount += 1;
@ -514,7 +498,7 @@ pub(crate) fn blueprints_scenes_spawned(
}
}
// Mark all components as "Disabled" (until Bevy gets this as first class feature)
commands.entity(child).insert(BlueprintDisabled);
commands.entity(child).insert(BlueprintInstanceDisabled);
}
}
@ -543,7 +527,7 @@ pub struct BlueprintReadyForPostProcess;
/// - 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 adds an `AnimationLink` component containing the entity that has the AnimationPlayer so that animations can be controlled from the original entity
/// - it adds an `AnimationLink` component containing the entity that has the `AnimationPlayer` so that animations can be controlled from the original entity
pub(crate) fn blueprints_cleanup_spawned_scene(
blueprint_scenes: Query<
(
@ -552,7 +536,6 @@ pub(crate) fn blueprints_cleanup_spawned_scene(
&OriginalChildren,
Option<&Name>,
&BlueprintAnimations,
Option<&NoInBlueprint>,
),
Added<BlueprintChildrenReady>,
>,
@ -567,7 +550,7 @@ pub(crate) fn blueprints_cleanup_spawned_scene(
all_names: Query<&Name>,
) {
for (original, children, original_children, name, animations, no_inblueprint) in
for (original, children, original_children, name, animations) in
blueprint_scenes.iter()
{
info!("YOOO ready !! removing empty nodes {:?}", name);
@ -589,11 +572,10 @@ pub(crate) fn blueprints_cleanup_spawned_scene(
// 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(blueprint_root_entity) {
commands.entity(child).insert(InBlueprint); // we do this here in order to avoid doing it to normal children
}
for child in all_children.iter_descendants(blueprint_root_entity) {
commands.entity(child).insert(InBlueprint); // we do this here in order to avoid doing it to normal children
}
// copy components into from blueprint instance's blueprint_root_entity to original entity
commands.add(CopyComponents {
@ -762,7 +744,7 @@ pub(crate) fn blueprints_finalize_instances(
}
for child in all_children.iter_descendants(entity) {
commands.entity(child).remove::<BlueprintDisabled>();
commands.entity(child).remove::<BlueprintInstanceDisabled>();
}
if hide_until_ready.is_some() {

View File

@ -23,7 +23,7 @@ use bevy::{
/// # use bevy::gltf::*;
/// # use bevy_gltf_components::ComponentsFromGltfPlugin;
///
/// //too barebones of an example to be meaningfull, please see https://github.com/kaosat-dev/Blender_bevy_components_workflow/examples/basic for a real example
/// //too barebones of an example to be meaningfull, please see https://github.com/kaosat-dev/Blenvy/examples/basic for a real example
/// fn main() {
/// App::new()
/// .add_plugins(DefaultPlugins)

View File

@ -22,6 +22,10 @@ pub struct BlenvyConfig {
pub(crate) aabbs: bool,
pub(crate) aabb_cache: HashMap<String, Aabb>, // cache for aabbs
pub(crate) materials_cache: HashMap<String, Handle<StandardMaterial>>, // cache for materials
// save & load
pub(crate) save_component_filter: SceneFilter,
pub(crate) save_resource_filter: SceneFilter,
}
#[derive(Debug, Clone)]
@ -34,6 +38,10 @@ pub struct BlenvyPlugin {
/// Automatically generate aabbs for the blueprints root objects
pub aabbs: bool,
// for save & load
pub save_component_filter: SceneFilter,
pub save_resource_filter: SceneFilter,
}
impl Default for BlenvyPlugin {
@ -43,6 +51,9 @@ impl Default for BlenvyPlugin {
registry_component_filter: SceneFilter::default(),
registry_resource_filter: SceneFilter::default(),
aabbs: false,
save_component_filter: SceneFilter::default(),
save_resource_filter: SceneFilter::default(),
}
}
}
@ -63,6 +74,9 @@ impl Plugin for BlenvyPlugin {
aabb_cache: HashMap::new(),
materials_cache: HashMap::new(),
save_component_filter: self.save_component_filter.clone(),
save_resource_filter: self.save_resource_filter.clone()
});
}
}

View File

@ -1,12 +1,10 @@
[package]
name = "bevy_gltf_blueprints_animation_example"
version = "0.3.0"
name = "blenvy_animation_example"
version = "0.0.1"
edition = "2021"
license = "MIT OR Apache-2.0"
[dependencies]
bevy = { version = "0.13", features = ["dynamic_linking"] }
bevy_gltf_blueprints = { path = "../../../crates/bevy_gltf_blueprints" }
bevy_gltf_worlflow_examples_common_rapier = { path = "../../common_rapier" }
bevy_rapier3d = { version = "0.25.0", features = ["serde-serialize", "debug-render-3d", "enhanced-determinism"] }
rand = "0.8.5"
bevy = { version = "0.14", features = ["dynamic_linking"] }
blenvy = { path = "../../../crates/blenvy" }
rand = "0.8.5"

View File

@ -1,6 +0,0 @@
({
"world":File (path: "models/Level1.glb"),
"models": Folder (
path: "models/library",
),
})

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -60,8 +60,8 @@ pub enum EnumTest {
None,
}
pub struct ComponentsTestPlugin;
impl Plugin for ComponentsTestPlugin {
pub struct ComponentsExamplesPlugin;
impl Plugin for ComponentsExamplesPlugin {
fn build(&self, app: &mut App) {
app.register_type::<BasicTest>()
.register_type::<UnitTest>()

View File

@ -1,12 +0,0 @@
use bevy::prelude::*;
use bevy_gltf_blueprints::*;
pub struct CorePlugin;
impl Plugin for CorePlugin {
fn build(&self, app: &mut App) {
app.add_plugins((BlueprintsPlugin {
aabbs: true,
..Default::default()
},));
}
}

View File

@ -1,320 +0,0 @@
use bevy_gltf_worlflow_examples_common_rapier::{
assets::GameAssets, GameState, InAppRunning, Player,
};
use bevy_rapier3d::prelude::Velocity;
use rand::Rng;
use std::time::Duration;
use bevy::prelude::*;
use bevy_gltf_blueprints::{
BluePrintBundle, BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintName, GameWorldTag,
};
use super::{Fox, Robot};
pub fn setup_game(
mut commands: Commands,
game_assets: Res<GameAssets>,
models: Res<Assets<bevy::gltf::Gltf>>,
mut next_game_state: ResMut<NextState<GameState>>,
) {
commands.insert_resource(AmbientLight {
color: Color::WHITE,
brightness: 0.2,
});
// here we actually spawn our game world/level
commands.spawn((
SceneBundle {
// 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.clone().unwrap().id())
.expect("main level should have been loaded")
.scenes[0]
.clone(),
..default()
},
bevy::prelude::Name::from("world"),
GameWorldTag,
InAppRunning,
));
next_game_state.set(GameState::InGame)
}
pub fn spawn_test(
keycode: Res<ButtonInput<KeyCode>>,
mut commands: Commands,
mut game_world: Query<(Entity, &Children), With<GameWorldTag>>,
) {
if keycode.just_pressed(KeyCode::KeyT) {
let world = game_world.single_mut();
let world = world.1[0];
let mut rng = rand::thread_rng();
let range = 8.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("Fox".to_string()),
..Default::default()
},
bevy::prelude::Name::from(format!("Spawned{}", name_index)),
// BlueprintName("Health_Pickup".to_string()),
// SpawnHere,
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),
},
))
.id();
commands.entity(world).add_child(new_entity);
}
}
// example of changing animation of entities based on proximity to the player, for "fox" entities (Tag component)
pub fn animation_change_on_proximity_foxes(
players: Query<&GlobalTransform, With<Player>>,
animated_foxes: Query<
(
&GlobalTransform,
&BlueprintAnimationPlayerLink,
&BlueprintAnimations,
),
With<Fox>,
>,
mut animation_players: Query<&mut AnimationPlayer>,
) {
for player_transforms in players.iter() {
for (fox_tranforms, link, animations) in animated_foxes.iter() {
let distance = player_transforms
.translation()
.distance(fox_tranforms.translation());
let mut anim_name = "Walk";
if distance < 8.5 {
anim_name = "Run";
} else if (8.5..10.0).contains(&distance) {
anim_name = "Walk";
} else if (10.0..15.0).contains(&distance) {
anim_name = "Survey";
}
// now play the animation based on the chosen animation name
let mut animation_player = animation_players.get_mut(link.0).unwrap();
animation_player
.play_with_transition(
animations
.named_animations
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(3),
)
.repeat();
}
}
}
// example of changing animation of entities based on proximity to the player, this time for the "robot" entities (Tag component)
pub fn animation_change_on_proximity_robots(
players: Query<&GlobalTransform, With<Player>>,
animated_robots: Query<
(
&GlobalTransform,
&BlueprintAnimationPlayerLink,
&BlueprintAnimations,
),
With<Robot>,
>,
mut animation_players: Query<&mut AnimationPlayer>,
) {
for player_transforms in players.iter() {
for (robot_tranforms, link, animations) in animated_robots.iter() {
let distance = player_transforms
.translation()
.distance(robot_tranforms.translation());
let mut anim_name = "Idle";
if distance < 8.5 {
anim_name = "Jump";
} else if (8.5..10.0).contains(&distance) {
anim_name = "Scan";
} else if (10.0..15.0).contains(&distance) {
anim_name = "Idle";
}
// now play the animation based on the chosen animation name
let mut animation_player = animation_players.get_mut(link.0).unwrap();
animation_player
.play_with_transition(
animations
.named_animations
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(3),
)
.repeat();
}
}
}
pub fn animation_control(
animated_enemies: Query<(&BlueprintAnimationPlayerLink, &BlueprintAnimations), With<Robot>>,
animated_foxes: Query<(&BlueprintAnimationPlayerLink, &BlueprintAnimations), With<Fox>>,
mut animation_players: Query<&mut AnimationPlayer>,
keycode: Res<ButtonInput<KeyCode>>,
// mut entities_with_animations : Query<(&mut AnimationPlayer, &mut BlueprintAnimations)>,
) {
// robots
if keycode.just_pressed(KeyCode::KeyB) {
for (link, animations) in animated_enemies.iter() {
let mut animation_player = animation_players.get_mut(link.0).unwrap();
let anim_name = "Scan";
animation_player
.play_with_transition(
animations
.named_animations
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(5),
)
.repeat();
}
}
// foxes
if keycode.just_pressed(KeyCode::KeyW) {
for (link, animations) in animated_foxes.iter() {
let mut animation_player = animation_players.get_mut(link.0).unwrap();
let anim_name = "Walk";
animation_player
.play_with_transition(
animations
.named_animations
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(5),
)
.repeat();
}
}
if keycode.just_pressed(KeyCode::KeyX) {
for (link, animations) in animated_foxes.iter() {
let mut animation_player = animation_players.get_mut(link.0).unwrap();
let anim_name = "Run";
animation_player
.play_with_transition(
animations
.named_animations
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(5),
)
.repeat();
}
}
if keycode.just_pressed(KeyCode::KeyC) {
for (link, animations) in animated_foxes.iter() {
let mut animation_player = animation_players.get_mut(link.0).unwrap();
let anim_name = "Survey";
animation_player
.play_with_transition(
animations
.named_animations
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(5),
)
.repeat();
}
}
/* Improveement ideas for the future
// a bit more ideal API
if keycode.just_pressed(KeyCode::B) {
for (animation_player, animations) in animated_enemies.iter() {
let anim_name = "Scan";
if animations.named_animations.contains_key(anim_name) {
let clip = animations.named_animations.get(anim_name).unwrap();
animation_player.play_with_transition(clip.clone(), Duration::from_secs(5)).repeat();
}
}
}
// even better API
if keycode.just_pressed(KeyCode::B) {
for (animation_player, animations) in animated_enemies.iter() {
animation_player.play_with_transition("Scan", Duration::from_secs(5)).repeat(); // with a merged animationPlayer + animations storage
// alternative, perhaps more realistic, and better seperation of concerns
animation_player.play_with_transition(animations, "Scan", Duration::from_secs(5)).repeat();
}
}*/
/*for (mut anim_player, animations) in entities_with_animations.iter_mut(){
if keycode.just_pressed(KeyCode::W) {
let anim_name = "Walk";
if animations.named_animations.contains_key(anim_name) {
let clip = animations.named_animations.get(anim_name).unwrap();
anim_player.play_with_transition(clip.clone(), Duration::from_secs(5)).repeat();
}
}
if keycode.just_pressed(KeyCode::X) {
let anim_name = "Run";
if animations.named_animations.contains_key(anim_name) {
let clip = animations.named_animations.get(anim_name).unwrap();
anim_player.play_with_transition(clip.clone(), Duration::from_secs(5)).repeat();
}
}
if keycode.just_pressed(KeyCode::C) {
let anim_name = "Survey";
if animations.named_animations.contains_key(anim_name) {
let clip = animations.named_animations.get(anim_name).unwrap();
anim_player.play_with_transition(clip.clone(), Duration::from_secs(5)).repeat();
}
}
if keycode.just_pressed(KeyCode::S) {
let anim_name = "Scan";
if animations.named_animations.contains_key(anim_name) {
let clip = animations.named_animations.get(anim_name).unwrap();
anim_player.play_with_transition(clip.clone(), Duration::from_secs(5)).repeat();
}
}
if keycode.just_pressed(KeyCode::I) {
let anim_name = "Idle";
if animations.named_animations.contains_key(anim_name) {
let clip = animations.named_animations.get(anim_name).unwrap();
anim_player.play_with_transition(clip.clone(), Duration::from_secs(5)).repeat();
}
}
}*/
}

View File

@ -1,40 +0,0 @@
pub mod in_game;
pub use in_game::*;
pub mod in_main_menu;
pub use in_main_menu::*;
use bevy::prelude::*;
use bevy_gltf_worlflow_examples_common_rapier::{AppState, GameState};
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// Demo marker component
pub struct Fox;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// Demo marker component
pub struct Robot;
pub struct GamePlugin;
impl Plugin for GamePlugin {
fn build(&self, app: &mut App) {
app.register_type::<Robot>()
.register_type::<Fox>()
.add_systems(
Update,
(
spawn_test,
animation_control,
animation_change_on_proximity_foxes,
animation_change_on_proximity_robots,
)
.run_if(in_state(GameState::InGame)),
)
.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(AppState::AppRunning), setup_game);
}
}

View File

@ -1,24 +1,217 @@
use std::time::Duration;
use bevy::prelude::*;
use bevy_gltf_worlflow_examples_common_rapier::CommonPlugin;
use blenvy::{AddToGameWorld, BlenvyPlugin, BluePrintBundle, BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintInfo, DynamicBlueprintInstance, GameWorldTag, HideUntilReady, SpawnBlueprint};
use rand::Rng;
mod core;
use crate::core::*;
mod component_examples;
use component_examples::*;
mod game;
use game::*;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// Demo marker component
pub struct Player;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// Demo marker component
pub struct Fox;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// Demo marker component
pub struct Robot;
mod test_components;
use test_components::*;
fn main() {
App::new()
.add_plugins((
DefaultPlugins.set(AssetPlugin::default()),
// our custom plugins
CommonPlugin,
CorePlugin, // reusable plugins
GamePlugin, // specific to our game
ComponentsTestPlugin, // Showcases different type of components /structs
ComponentsExamplesPlugin, // Showcases different type of components /structs
BlenvyPlugin::default()
))
.register_type::<Player>()
.register_type::<Fox>()
.register_type::<Robot>()
.add_systems(Startup, setup_game)
.add_systems(Update,
(animation_control,)
)
.run();
}
// this is how you setup & spawn a level from a blueprint
fn setup_game(
mut commands: Commands,
) {
// here we actually spawn our game world/level
commands.spawn((
BlueprintInfo::from_path("levels/World.glb"), // all we need is a Blueprint info...
SpawnBlueprint, // and spawnblueprint to tell blenvy to spawn the blueprint now
HideUntilReady, // only reveal the level once it is ready
GameWorldTag,
));
}
//////////////////////////////////
pub fn animation_control(
animated_robots: Query<(&BlueprintAnimationPlayerLink, &BlueprintAnimations), With<Robot>>,
animated_foxes: Query<(&BlueprintAnimationPlayerLink, &BlueprintAnimations), With<Fox>>,
mut animation_players: Query<(&mut AnimationPlayer, &mut AnimationTransitions)>,
keycode: Res<ButtonInput<KeyCode>>,
// mut entities_with_animations : Query<(&mut AnimationPlayer, &mut BlueprintAnimations)>,
) {
// robots
if keycode.just_pressed(KeyCode::KeyB) {
println!("scan animation for robots");
for (link, animations) in animated_robots.iter() {
let (mut animation_player, mut animation_transitions) =
animation_players.get_mut(link.0).unwrap();
println!("got some animations");
let anim_name = "Scan";
animation_transitions
.play(
&mut animation_player,
animations
.named_indices
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(5),
)
.repeat();
}
}
// foxes
if keycode.just_pressed(KeyCode::KeyW) {
for (link, animations) in animated_foxes.iter() {
let (mut animation_player, mut animation_transitions) =
animation_players.get_mut(link.0).unwrap();
let anim_name = "Walk";
animation_transitions
.play(
&mut animation_player,
animations
.named_indices
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(5),
)
.repeat();
}
}
if keycode.just_pressed(KeyCode::KeyX) {
for (link, animations) in animated_foxes.iter() {
let (mut animation_player, mut animation_transitions) =
animation_players.get_mut(link.0).unwrap();
let anim_name = "Run";
animation_transitions
.play(
&mut animation_player,
animations
.named_indices
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(5),
)
.repeat();
}
}
if keycode.just_pressed(KeyCode::KeyC) {
for (link, animations) in animated_foxes.iter() {
let (mut animation_player, mut animation_transitions) =
animation_players.get_mut(link.0).unwrap();
let anim_name = "Survey";
animation_transitions
.play(
&mut animation_player,
animations
.named_indices
.get(anim_name)
.expect("animation name should be in the list")
.clone(),
Duration::from_secs(5),
)
.repeat();
}
}
/* Improveement ideas for the future
// a bit more ideal API
if keycode.just_pressed(KeyCode::B) {
for (animation_player, animations) in animated_robots.iter() {
let anim_name = "Scan";
if animations.named_animations.contains_key(anim_name) {
let clip = animations.named_animations.get(anim_name).unwrap();
animation_player.play_with_transition(clip.clone(), Duration::from_secs(5)).repeat();
}
}
}
// even better API
if keycode.just_pressed(KeyCode::B) {
for (animation_player, animations) in animated_robots.iter() {
animation_player.play_with_transition("Scan", Duration::from_secs(5)).repeat(); // with a merged animationPlayer + animations storage
// alternative, perhaps more realistic, and better seperation of concerns
animation_player.play_with_transition(animations, "Scan", Duration::from_secs(5)).repeat();
}
}*/
/*for (mut anim_player, animations) in entities_with_animations.iter_mut(){
if keycode.just_pressed(KeyCode::W) {
let anim_name = "Walk";
if animations.named_animations.contains_key(anim_name) {
let clip = animations.named_animations.get(anim_name).unwrap();
anim_player.play_with_transition(clip.clone(), Duration::from_secs(5)).repeat();
}
}
if keycode.just_pressed(KeyCode::X) {
let anim_name = "Run";
if animations.named_animations.contains_key(anim_name) {
let clip = animations.named_animations.get(anim_name).unwrap();
anim_player.play_with_transition(clip.clone(), Duration::from_secs(5)).repeat();
}
}
if keycode.just_pressed(KeyCode::C) {
let anim_name = "Survey";
if animations.named_animations.contains_key(anim_name) {
let clip = animations.named_animations.get(anim_name).unwrap();
anim_player.play_with_transition(clip.clone(), Duration::from_secs(5)).repeat();
}
}
if keycode.just_pressed(KeyCode::S) {
let anim_name = "Scan";
if animations.named_animations.contains_key(anim_name) {
let clip = animations.named_animations.get(anim_name).unwrap();
anim_player.play_with_transition(clip.clone(), Duration::from_secs(5)).repeat();
}
}
if keycode.just_pressed(KeyCode::I) {
let anim_name = "Idle";
if animations.named_animations.contains_key(anim_name) {
let clip = animations.named_animations.get(anim_name).unwrap();
anim_player.play_with_transition(clip.clone(), Duration::from_secs(5)).repeat();
}
}
}*/
}

View File

@ -1,54 +0,0 @@
use bevy::app::AppExit;
use bevy::prelude::*;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default, States)]
pub enum AppState {
#[default]
CoreLoading,
MenuRunning,
AppLoading,
AppRunning,
AppEnding,
// FIXME: not sure
LoadingGame,
}
#[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 InAppLoading;
#[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;
pub struct StatePlugin;
impl Plugin for StatePlugin {
fn build(&self, app: &mut App) {
app.add_state::<AppState>().add_state::<GameState>();
}
}

View File

@ -1,12 +0,0 @@
[package]
name = "bevy_gltf_blueprints_basic_example"
version = "0.3.0"
edition = "2021"
license = "MIT OR Apache-2.0"
[dependencies]
bevy = { version = "0.13", features = ["dynamic_linking"] }
bevy_gltf_blueprints = { path = "../../../crates/bevy_gltf_blueprints" }
bevy_gltf_worlflow_examples_common_rapier = { path = "../../common_rapier" }
bevy_rapier3d = { version = "0.25.0", features = ["serde-serialize", "debug-render-3d", "enhanced-determinism"] }
rand = "0.8.5"

View File

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

View File

@ -1,12 +0,0 @@
use bevy::prelude::*;
use bevy_gltf_blueprints::*;
pub struct CorePlugin;
impl Plugin for CorePlugin {
fn build(&self, app: &mut App) {
app.add_plugins((BlueprintsPlugin {
aabbs: true,
..Default::default()
},));
}
}

View File

@ -1,126 +0,0 @@
use bevy::prelude::*;
use bevy_gltf_blueprints::{BluePrintBundle, BlueprintName, GameWorldTag};
use bevy_gltf_worlflow_examples_common_rapier::{assets::GameAssets, GameState, InAppRunning};
use bevy_rapier3d::prelude::Velocity;
use rand::Rng;
pub fn setup_game(
mut commands: Commands,
game_assets: Res<GameAssets>,
models: Res<Assets<bevy::gltf::Gltf>>,
mut next_game_state: ResMut<NextState<GameState>>,
) {
commands.insert_resource(AmbientLight {
color: Color::WHITE,
brightness: 0.2,
});
// here we actually spawn our game world/level
commands.spawn((
SceneBundle {
// 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.clone().unwrap().id())
.expect("main level should have been loaded")
.scenes[0]
.clone(),
..default()
},
bevy::prelude::Name::from("world"),
GameWorldTag,
InAppRunning,
));
next_game_state.set(GameState::InGame)
}
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct UnregisteredComponent;
pub fn spawn_test(
keycode: Res<ButtonInput<KeyCode>>,
mut commands: Commands,
mut game_world: Query<(Entity, &Children), With<GameWorldTag>>,
) {
if keycode.just_pressed(KeyCode::KeyT) {
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()),
..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 fn spawn_test_unregisted_components(
keycode: Res<ButtonInput<KeyCode>>,
mut commands: Commands,
mut game_world: Query<(Entity, &Children), With<GameWorldTag>>,
) {
if keycode.just_pressed(KeyCode::KeyU) {
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()),
..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);
}
}

View File

@ -1,121 +0,0 @@
use bevy::prelude::*;
use bevy_gltf_worlflow_examples_common_rapier::{AppState, InMainMenu};
pub fn setup_main_menu(mut commands: Commands) {
commands.spawn((
Camera2dBundle {
camera: Camera {
order: 102, // needed because of this: https://github.com/jakobhellermann/bevy_editor_pls/blob/crates/bevy_editor_pls_default_windows/src/cameras/mod.rs#L213C9-L213C28
..default()
},
..Default::default()
},
InMainMenu,
));
commands.spawn((
TextBundle::from_section(
"SOME GAME TITLE !!",
TextStyle {
//font: asset_server.load("fonts/FiraMono-Medium.ttf"),
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 T once the game is started for demo spawning)",
TextStyle {
//font: asset_server.load("fonts/FiraMono-Medium.ttf"),
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: asset_server.load("fonts/FiraMono-Medium.ttf"),
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: asset_server.load("fonts/FiraMono-Medium.ttf"),
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(bla: Query<Entity, With<InMainMenu>>, mut commands: Commands) {
for bli in bla.iter() {
commands.entity(bli).despawn_recursive();
}
}
pub fn main_menu(
keycode: Res<ButtonInput<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::Enter) {
next_app_state.set(AppState::AppLoading);
// next_game_state.set(GameState::None);
}
if keycode.just_pressed(KeyCode::KeyL) {
next_app_state.set(AppState::AppLoading);
// load_requested_events.send(LoadRequest { path: "toto".into() })
}
if keycode.just_pressed(KeyCode::KeyS) {
// save_requested_events.send(SaveRequest { path: "toto".into() })
}
}

View File

@ -1,22 +0,0 @@
pub mod in_game;
pub use in_game::*;
pub mod in_main_menu;
pub use in_main_menu::*;
use bevy::prelude::*;
use bevy_gltf_worlflow_examples_common_rapier::{AppState, GameState};
pub struct GamePlugin;
impl Plugin for GamePlugin {
fn build(&self, app: &mut App) {
app.add_systems(
Update,
(spawn_test, spawn_test_unregisted_components).run_if(in_state(GameState::InGame)),
)
.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(AppState::AppRunning), setup_game);
}
}

View File

@ -1,80 +0,0 @@
use bevy::prelude::*;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct UnitTest;
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
#[reflect(Component)]
struct TupleTestF32(f32);
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
#[reflect(Component)]
struct TupleTestU64(u64);
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
#[reflect(Component)]
pub struct TupleTestStr(String);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct TupleTest2(f32, u64, String);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct TupleTestBool(bool);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct TupleVec2(Vec2);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct TupleVec3(Vec3);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct TupleVec(Vec<String>);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct TupleTestColor(Color);
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct BasicTest {
a: f32,
b: u64,
c: String,
}
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub enum EnumTest {
Metal,
Wood,
Rock,
Cloth,
Squishy,
#[default]
None,
}
pub struct ComponentsTestPlugin;
impl Plugin for ComponentsTestPlugin {
fn build(&self, app: &mut App) {
app.register_type::<BasicTest>()
.register_type::<UnitTest>()
.register_type::<TupleTestF32>()
.register_type::<TupleTestU64>()
.register_type::<TupleTestStr>()
.register_type::<TupleTestBool>()
.register_type::<TupleTest2>()
.register_type::<TupleVec2>()
.register_type::<TupleVec3>()
.register_type::<EnumTest>()
.register_type::<TupleTestColor>()
.register_type::<TupleVec>()
.register_type::<Vec<String>>();
}
}

View File

@ -1,12 +0,0 @@
[package]
name = "bevy_gltf_blueprints_basic_xpbd_physics_example"
version = "0.3.0"
edition = "2021"
license = "MIT OR Apache-2.0"
[dependencies]
bevy = { version = "0.13", features = ["dynamic_linking"] }
bevy_gltf_blueprints = { path = "../../../crates/bevy_gltf_blueprints" }
bevy_gltf_worlflow_examples_common_xpbd = { path = "../../common_xpbd" }
bevy_xpbd_3d = "0.4"
rand = "0.8.5"

View File

@ -1,15 +0,0 @@
# Basic xpbd physics example/demo
Same as the basic example but using [xpbd](https://github.com/Jondolf/bevy_xpbd) instead of Rapier [rapier](https://github.com/dimforge/bevy_rapier)
## Running this example
```
cargo run --features bevy/dynamic_linking
```
### Additional notes
* You usually define either the Components directly or use ```Proxy components``` that get replaced in Bevy systems with the actual Components that you want (usually when for some reason, ie external crates with unregistered components etc) you cannot use the components directly.
* this example contains code for future features, not finished yet ! please disregard anything related to saving & loading

View File

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

View File

@ -1,11 +0,0 @@
use bevy::prelude::*;
use bevy_gltf_blueprints::*;
pub struct CorePlugin;
impl Plugin for CorePlugin {
fn build(&self, app: &mut App) {
app.add_plugins((BlueprintsPlugin {
..Default::default()
},));
}
}

View File

@ -1,76 +0,0 @@
use bevy::prelude::*;
use bevy_gltf_blueprints::{BluePrintBundle, BlueprintName, GameWorldTag};
use bevy_gltf_worlflow_examples_common_xpbd::{assets::GameAssets, GameState, InAppRunning};
use bevy_xpbd_3d::prelude::*;
use rand::Rng;
pub fn setup_game(
mut commands: Commands,
game_assets: Res<GameAssets>,
models: Res<Assets<bevy::gltf::Gltf>>,
mut next_game_state: ResMut<NextState<GameState>>,
) {
commands.insert_resource(AmbientLight {
color: Color::WHITE,
brightness: 0.2,
});
// here we actually spawn our game world/level
commands.spawn((
SceneBundle {
// 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.clone().unwrap().id())
.expect("main level should have been loaded")
.scenes[0]
.clone(),
..default()
},
bevy::prelude::Name::from("world"),
GameWorldTag,
InAppRunning,
));
next_game_state.set(GameState::InGame)
}
pub fn spawn_test(
keycode: Res<ButtonInput<KeyCode>>,
mut commands: Commands,
mut game_world: Query<(Entity, &Children), With<GameWorldTag>>,
) {
if keycode.just_pressed(KeyCode::KeyT) {
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()),
..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)),
LinearVelocity(Vec3::new(vel_x, vel_y, vel_z)),
AngularVelocity::ZERO,
))
.id();
commands.entity(world).add_child(new_entity);
}
}

View File

@ -1,107 +0,0 @@
use bevy::prelude::*;
use bevy_gltf_worlflow_examples_common_xpbd::{AppState, InMainMenu};
pub fn setup_main_menu(mut commands: Commands) {
commands.spawn((
Camera2dBundle {
camera: Camera {
order: 102, // needed because of this: https://github.com/jakobhellermann/bevy_editor_pls/blob/crates/bevy_editor_pls_default_windows/src/cameras/mod.rs#L213C9-L213C28
..default()
},
..Default::default()
},
InMainMenu,
));
commands.spawn((
TextBundle::from_section(
"SOME GAME TITLE !!",
TextStyle {
//font: asset_server.load("fonts/FiraMono-Medium.ttf"),
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 T once the game is started for demo spawning)",
TextStyle {
//font: asset_server.load("fonts/FiraMono-Medium.ttf"),
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: asset_server.load("fonts/FiraMono-Medium.ttf"),
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: asset_server.load("fonts/FiraMono-Medium.ttf"),
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(bla: Query<Entity, With<InMainMenu>>, mut commands: Commands) {
for bli in bla.iter() {
commands.entity(bli).despawn_recursive();
}
}
pub fn main_menu(
keycode: Res<ButtonInput<KeyCode>>,
mut next_app_state: ResMut<NextState<AppState>>,
) {
if keycode.just_pressed(KeyCode::Enter) {
next_app_state.set(AppState::AppLoading);
}
}

View File

@ -1,19 +0,0 @@
pub mod in_game;
pub use in_game::*;
pub mod in_main_menu;
pub use in_main_menu::*;
use bevy::prelude::*;
use bevy_gltf_worlflow_examples_common_xpbd::{AppState, GameState};
pub struct GamePlugin;
impl Plugin for GamePlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, (spawn_test,).run_if(in_state(GameState::InGame)))
.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(AppState::AppRunning), setup_game);
}
}

View File

@ -1,24 +0,0 @@
use bevy::prelude::*;
use bevy_gltf_worlflow_examples_common_xpbd::CommonPlugin;
mod core;
use crate::core::*;
mod game;
use game::*;
mod test_components;
use test_components::*;
fn main() {
App::new()
.add_plugins((
DefaultPlugins.set(AssetPlugin::default()),
// our custom plugins
CommonPlugin,
CorePlugin, // reusable plugins
GamePlugin, // specific to our game
ComponentsTestPlugin, // Showcases different type of components /structs
))
.run();
}

View File

@ -0,0 +1,10 @@
[package]
name = "blenvy_blueprints_example"
version = "0.0.1"
edition = "2021"
license = "MIT OR Apache-2.0"
[dependencies]
bevy = { version = "0.14", features = ["dynamic_linking"] }
blenvy = { path = "../../../crates/blenvy" }
rand = "0.8.5"

View File

@ -2,7 +2,7 @@
# Materials example/demo
Example of materials use & reuse (including textures) to avoid redundant materials in blueprints gltfs that lead to asset & memory bloat
- to be used together with ```gltf_auto_export``` version >0.6 with the "materials library" option for exports
- to be used together with ```blenvy``` version >0.6 with the "materials library" option for exports
- It shows you how ou can configure```Bevy_gltf_blueprints``` to support material libraries
- material library is [here](./assets/materials/)

View File

Before

Width:  |  Height:  |  Size: 243 KiB

After

Width:  |  Height:  |  Size: 243 KiB

View File

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 174 KiB

View File

@ -56,8 +56,8 @@ pub enum EnumTest {
None,
}
pub struct ComponentsTestPlugin;
impl Plugin for ComponentsTestPlugin {
pub struct ComponentsExamplesPlugin;
impl Plugin for ComponentsExamplesPlugin {
fn build(&self, app: &mut App) {
app.register_type::<BasicTest>()
.register_type::<TupleTestF32>()

View File

@ -0,0 +1,76 @@
use bevy::prelude::*;
use blenvy::{AddToGameWorld, BlenvyPlugin, BluePrintBundle, BlueprintInfo, DynamicBlueprintInstance, GameWorldTag, HideUntilReady, SpawnBlueprint};
use rand::Rng;
mod component_examples;
use component_examples::*;
fn main() {
App::new()
.add_plugins((
DefaultPlugins.set(AssetPlugin::default()),
// our custom plugins
ComponentsExamplesPlugin, // Showcases different type of components /structs
BlenvyPlugin::default()
))
.add_systems(Startup, setup_game)
.add_systems(Update, spawn_blueprint_instance)
.run();
}
// this is how you setup & spawn a level from a blueprint
fn setup_game(
mut commands: Commands,
) {
// here we spawn our game world/level, which is also a blueprint !
commands.spawn((
BlueprintInfo::from_path("levels/World.glb"), // all we need is a Blueprint info...
SpawnBlueprint, // and spawnblueprint to tell blenvy to spawn the blueprint now
HideUntilReady, // only reveal the level once it is ready
GameWorldTag,
));
}
// you can also spawn blueprint instances at runtime
pub fn spawn_blueprint_instance(
keycode: Res<ButtonInput<KeyCode>>,
mut commands: Commands,
mut game_world: Query<(Entity, &Children), With<GameWorldTag>>,
) {
if keycode.just_pressed(KeyCode::KeyS) {
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 name_index: u64 = rng.gen();
let new_entity = commands
.spawn((
BluePrintBundle {
blueprint: BlueprintInfo {
name: "spawned".into(),
path: "blueprints/Blueprint 3.gltf".into(),
}, // FIXME
..Default::default()
},
DynamicBlueprintInstance,
bevy::prelude::Name::from(format!("test{}", name_index)),
HideUntilReady,
AddToGameWorld,
TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)),
))
.id();
// commands.entity(world).add_child(new_entity);
}
}

View File

@ -0,0 +1,9 @@
[package]
name = "blenvy_components_example"
version = "0.0.1"
edition = "2021"
license = "MIT OR Apache-2.0"
[dependencies]
bevy = { version = "0.14", features = ["dynamic_linking"] }
blenvy = { path = "../../../crates/blenvy" }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More