mirror of
https://github.com/kaosat-dev/Blender_bevy_components_workflow.git
synced 2024-12-26 01:24:10 +00:00
feat(Animation): added code & example for animation support (#33)
* feat(animation): added example & boilerplate * moved animations specific code to a different module * added multiple robots & foxes * added example of controlling animation based on distance from the player * removed obsolete files * added information about animation to READMEs * updated dependencies closes #26
This commit is contained in:
parent
ea1d6cd78f
commit
4afa0f5d7d
996
Cargo.lock
generated
996
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -14,7 +14,7 @@ members = [
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
bevy="0.11"
|
bevy="0.11"
|
||||||
bevy_rapier3d = { version = "0.22.0", features = [ "serde-serialize", "debug-render-3d", "enhanced-determinism"] }
|
bevy_rapier3d = { version = "0.22.0", features = [ "serde-serialize", "debug-render-3d", "enhanced-determinism"] }
|
||||||
bevy_editor_pls = { git="https://github.com/jakobhellermann/bevy_editor_pls.git" }
|
bevy_editor_pls = { version = "0.5.0" }
|
||||||
bevy_asset_loader = { version = "0.17.0", features = ["standard_dynamic_assets" ]} #version = "0.16",
|
bevy_asset_loader = { version = "0.17.0", features = ["standard_dynamic_assets" ]} #version = "0.16",
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
|
||||||
@ -30,6 +30,10 @@ ron="*"
|
|||||||
name = "basic"
|
name = "basic"
|
||||||
path = "examples/basic/main.rs"
|
path = "examples/basic/main.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "animation"
|
||||||
|
path = "examples/animation/main.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "advanced"
|
name = "advanced"
|
||||||
path = "examples/advanced/main.rs"
|
path = "examples/advanced/main.rs"
|
||||||
|
@ -50,6 +50,7 @@ Please read the [README]((./tools/gltf_auto_export/README.md)) of the add-on for
|
|||||||
|
|
||||||
- [basic](./examples/basic/) use of ```bevy_gltf_components``` only, to spawn entities with components defined inside gltf files
|
- [basic](./examples/basic/) use of ```bevy_gltf_components``` only, to spawn entities with components defined inside gltf files
|
||||||
- [advanced](./examples/advanced/) more advanced example : use of ```bevy_gltf_blueprints``` to spawn a level and then populate it with entities coming from different gltf files, live (at runtime) spawning of entities etc
|
- [advanced](./examples/advanced/) more advanced example : use of ```bevy_gltf_blueprints``` to spawn a level and then populate it with entities coming from different gltf files, live (at runtime) spawning of entities etc
|
||||||
|
- [animation](./examples/animation/) how to use and trigger animations from gltf files (a feature of ```bevy_gltf_blueprints```)
|
||||||
|
|
||||||
## Workflow
|
## Workflow
|
||||||
|
|
||||||
|
BIN
assets/animation/animation.blend
Normal file
BIN
assets/animation/animation.blend
Normal file
Binary file not shown.
1
assets/animation/assets_core.assets.ron
Normal file
1
assets/animation/assets_core.assets.ron
Normal file
@ -0,0 +1 @@
|
|||||||
|
({})
|
6
assets/animation/assets_game.assets.ron
Normal file
6
assets/animation/assets_game.assets.ron
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
({
|
||||||
|
"world":File (path: "animation/models/Level1.glb#Scene0"),
|
||||||
|
"models": Folder (
|
||||||
|
path: "animation/models/library",
|
||||||
|
),
|
||||||
|
})
|
BIN
assets/animation/models/Level1.glb
Normal file
BIN
assets/animation/models/Level1.glb
Normal file
Binary file not shown.
BIN
assets/animation/models/animation.glb
Normal file
BIN
assets/animation/models/animation.glb
Normal file
Binary file not shown.
BIN
assets/animation/models/library/Container.glb
Normal file
BIN
assets/animation/models/library/Container.glb
Normal file
Binary file not shown.
BIN
assets/animation/models/library/Fox.glb
Normal file
BIN
assets/animation/models/library/Fox.glb
Normal file
Binary file not shown.
BIN
assets/animation/models/library/Health_Pickup.glb
Normal file
BIN
assets/animation/models/library/Health_Pickup.glb
Normal file
Binary file not shown.
BIN
assets/animation/models/library/MagicTeapot.glb
Normal file
BIN
assets/animation/models/library/MagicTeapot.glb
Normal file
Binary file not shown.
BIN
assets/animation/models/library/Pillar.glb
Normal file
BIN
assets/animation/models/library/Pillar.glb
Normal file
Binary file not shown.
BIN
assets/animation/models/library/Player.glb
Normal file
BIN
assets/animation/models/library/Player.glb
Normal file
Binary file not shown.
BIN
assets/animation/models/library/Wheelbot.glb
Normal file
BIN
assets/animation/models/library/Wheelbot.glb
Normal file
Binary file not shown.
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_gltf_blueprints"
|
name = "bevy_gltf_blueprints"
|
||||||
version = "0.1.4"
|
version = "0.2.1"
|
||||||
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"] }
|
bevy = { version = "0.11", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf", "bevy_animation", "animation"] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy_gltf_components = "0.1"
|
bevy_gltf_components = "0.1"
|
||||||
bevy = { version = "0.11", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf", "bevy_animation"] }
|
bevy = { version = "0.11", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf", "bevy_animation", "animation"] }
|
||||||
|
@ -7,6 +7,9 @@
|
|||||||
|
|
||||||
Built upon [bevy_gltf_components](https://crates.io/crates/bevy_gltf_components) this crate adds the ability to define Blueprints/Prefabs for [Bevy](https://bevyengine.org/) inside gltf files and spawn them in Bevy.
|
Built upon [bevy_gltf_components](https://crates.io/crates/bevy_gltf_components) this crate adds the ability to 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 !
|
||||||
|
|
||||||
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
|
* just a Gltf file with Gltf_extras specifying components
|
||||||
@ -23,7 +26,7 @@ Here's a minimal usage example:
|
|||||||
# Cargo.toml
|
# Cargo.toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy="0.11"
|
bevy="0.11"
|
||||||
bevy_gltf_blueprints = { version = "0.1"}
|
bevy_gltf_blueprints = { version = "0.2"}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -61,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.1"
|
bevy_gltf_blueprints = "0.2"
|
||||||
```
|
```
|
||||||
|
|
||||||
Or use `cargo add`:
|
Or use `cargo add`:
|
||||||
@ -179,9 +182,69 @@ Typically , the order of systems should be
|
|||||||
|
|
||||||
see https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/advanced for how to set it up correctly
|
see https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/advanced for how to set it up correctly
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Animation
|
||||||
|
|
||||||
|
```bevy_gltf_blueprints``` provides some lightweight helpers to deal with animations stored in gltf files
|
||||||
|
|
||||||
|
* an ```Animations``` component that gets inserted into spawned (root) entities that contains a hashmap of all animations contained inside that entity/gltf file .
|
||||||
|
(this is a copy of the ```named_animations``` inside Bevy's gltf structures )
|
||||||
|
* an ```AnimationPlayerLink``` component that gets inserted into spawned (root) entities, to make it easier to trigger/ control animations than it usually is inside Bevy + Gltf files
|
||||||
|
|
||||||
|
The workflow for animations is as follows:
|
||||||
|
* create a gltf file with animations (using Blender & co) as you would normally do
|
||||||
|
* inside Bevy, use the ```bevy_gltf_blueprints``` boilerplate (see sections above), no specific setup beyond that is required
|
||||||
|
* to control the animation of an entity, you need to query for entities that have both ```AnimationPlayerLink``` and ```Animations``` components (added by ```bevy_gltf_blueprints```) AND entities with the ```AnimationPlayer``` component
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```rust no_run
|
||||||
|
// 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, &AnimationPlayerLink, &Animations ), 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 distance >= 8.5 && distance < 10.0{
|
||||||
|
anim_name = "Walk";
|
||||||
|
}
|
||||||
|
else if distance >= 10.0 && distance < 15.0{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
see https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/animation for how to set it up correctly
|
||||||
|
|
||||||
|
particularly from https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/animation/game/in_game.rs#86
|
||||||
|
onward
|
||||||
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/advanced
|
https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/advanced
|
||||||
|
https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/animation
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
16
crates/bevy_gltf_blueprints/src/animation.rs
Normal file
16
crates/bevy_gltf_blueprints/src/animation.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::utils::HashMap;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// storage for animations for a given entity (hierarchy), essentially a clone of gltf's named_animations
|
||||||
|
pub struct Animations {
|
||||||
|
pub named_animations: HashMap<String, Handle<AnimationClip>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
/// Stop gap helper component : this is inserted into a "root" entity (an entity representing a whole gltf file)
|
||||||
|
/// so that the root entity knows which of its children contains an actualy AnimationPlayer component
|
||||||
|
/// this is for convenience, because currently , Bevy's gltf parsing inserts AnimationPlayers "one level down"
|
||||||
|
/// ie armature/root for animated models, which means more complex queries to trigger animations that we want to avoid
|
||||||
|
pub struct AnimationPlayerLink(pub Entity);
|
@ -2,7 +2,10 @@ pub mod spawn_from_blueprints;
|
|||||||
pub use spawn_from_blueprints::*;
|
pub use spawn_from_blueprints::*;
|
||||||
|
|
||||||
pub mod spawn_post_process;
|
pub mod spawn_post_process;
|
||||||
pub use spawn_post_process::*;
|
pub(crate) use spawn_post_process::*;
|
||||||
|
|
||||||
|
pub mod animation;
|
||||||
|
pub use animation::*;
|
||||||
|
|
||||||
pub mod clone_entity;
|
pub mod clone_entity;
|
||||||
pub use clone_entity::*;
|
pub use clone_entity::*;
|
||||||
@ -59,6 +62,7 @@ impl Plugin for BlueprintsPlugin {
|
|||||||
app.add_plugins(ComponentsFromGltfPlugin)
|
app.add_plugins(ComponentsFromGltfPlugin)
|
||||||
.register_type::<BlueprintName>()
|
.register_type::<BlueprintName>()
|
||||||
.register_type::<SpawnHere>()
|
.register_type::<SpawnHere>()
|
||||||
|
.register_type::<Animations>()
|
||||||
.insert_resource(BluePrintsConfig {
|
.insert_resource(BluePrintsConfig {
|
||||||
library_folder: self.library_folder.clone(),
|
library_folder: self.library_folder.clone(),
|
||||||
})
|
})
|
||||||
|
@ -2,7 +2,7 @@ use std::path::Path;
|
|||||||
|
|
||||||
use bevy::{gltf::Gltf, prelude::*};
|
use bevy::{gltf::Gltf, prelude::*};
|
||||||
|
|
||||||
use crate::BluePrintsConfig;
|
use crate::{Animations, BluePrintsConfig};
|
||||||
|
|
||||||
/// this is a flag component for our levels/game world
|
/// this is a flag component for our levels/game world
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
@ -85,6 +85,9 @@ pub(crate) fn spawn_from_blueprints(
|
|||||||
// Parent(world) // FIXME/ would be good if this worked directly
|
// Parent(world) // FIXME/ would be good if this worked directly
|
||||||
SpawnedRoot,
|
SpawnedRoot,
|
||||||
Original(entity),
|
Original(entity),
|
||||||
|
Animations {
|
||||||
|
named_animations: gltf.named_animations.clone(),
|
||||||
|
},
|
||||||
))
|
))
|
||||||
.id();
|
.id();
|
||||||
commands.entity(world).add_child(child_scene);
|
commands.entity(world).add_child(child_scene);
|
||||||
|
@ -1,16 +1,9 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::utils::HashMap;
|
|
||||||
|
|
||||||
|
use super::{AnimationPlayerLink, Animations};
|
||||||
use super::{CloneEntity, SpawnHere};
|
use super::{CloneEntity, SpawnHere};
|
||||||
use super::{Original, SpawnedRoot};
|
use super::{Original, SpawnedRoot};
|
||||||
|
|
||||||
// FIXME: move to more relevant module
|
|
||||||
#[derive(Component, Reflect, Default, Debug)]
|
|
||||||
#[reflect(Component)]
|
|
||||||
pub struct AnimationHelper {
|
|
||||||
pub named_animations: HashMap<String, Handle<AnimationClip>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
/// FlagComponent for dynamically spawned scenes
|
/// FlagComponent for dynamically spawned scenes
|
||||||
pub(crate) struct SpawnedRootProcessed;
|
pub(crate) struct SpawnedRootProcessed;
|
||||||
@ -22,16 +15,16 @@ pub(crate) struct SpawnedRootProcessed;
|
|||||||
// - scene instance -> does not work
|
// - scene instance -> does not work
|
||||||
// it might be due to how we add components to the PARENT item in gltf to components
|
// it might be due to how we add components to the PARENT item in gltf to components
|
||||||
pub(crate) fn update_spawned_root_first_child(
|
pub(crate) fn update_spawned_root_first_child(
|
||||||
// all_children: Query<(Entity, &Children)>,
|
//
|
||||||
unprocessed_entities: Query<
|
unprocessed_entities: Query<
|
||||||
(Entity, &Children, &Name, &Parent, &Original),
|
(Entity, &Children, &Name, &Parent, &Original),
|
||||||
(With<SpawnedRoot>, Without<SpawnedRootProcessed>),
|
(With<SpawnedRoot>, Without<SpawnedRootProcessed>),
|
||||||
>,
|
>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
|
|
||||||
// FIXME: should be done at a more generic gltf level
|
// FIXME: not sure , but might be better if done at a more generic gltf level
|
||||||
animation_helpers: Query<&AnimationHelper>,
|
animations: Query<&Animations>,
|
||||||
added_animation_helpers: Query<(Entity, &AnimationPlayer), Added<AnimationPlayer>>,
|
added_animation_players: Query<(Entity, &Parent), Added<AnimationPlayer>>,
|
||||||
) {
|
) {
|
||||||
/*
|
/*
|
||||||
currently we have
|
currently we have
|
||||||
@ -84,14 +77,24 @@ pub(crate) fn update_spawned_root_first_child(
|
|||||||
|
|
||||||
// parent is either the world or an entity with a marker (BlueprintName)
|
// parent is either the world or an entity with a marker (BlueprintName)
|
||||||
commands.entity(parent.get()).add_child(*root_entity);
|
commands.entity(parent.get()).add_child(*root_entity);
|
||||||
|
|
||||||
let matching_animation_helper = animation_helpers.get(scene_instance);
|
|
||||||
|
|
||||||
if let Ok(anim_helper) = matching_animation_helper {
|
let matching_animations = animations.get(scene_instance);
|
||||||
for (added, _) in added_animation_helpers.iter() {
|
|
||||||
commands.entity(added).insert(AnimationHelper {
|
if let Ok(animations) = matching_animations {
|
||||||
named_animations: anim_helper.named_animations.clone(),
|
if animations.named_animations.keys().len() > 0 {
|
||||||
});
|
for (added, parent) in added_animation_players.iter() {
|
||||||
|
if parent.get() == *root_entity {
|
||||||
|
// FIXME: stopgap solution: since we cannot use an AnimationPlayer at the root entity level
|
||||||
|
// and we cannot update animation clips so that the EntityPaths point to one level deeper,
|
||||||
|
// BUT we still want to have some marker/control at the root entity level, we add this
|
||||||
|
commands
|
||||||
|
.entity(*root_entity)
|
||||||
|
.insert(AnimationPlayerLink(added));
|
||||||
|
commands.entity(*root_entity).insert(Animations {
|
||||||
|
named_animations: animations.named_animations.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
examples/animation/README.md
Normal file
16
examples/animation/README.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
# Animation demo
|
||||||
|
|
||||||
|
Example on how to control animations & how to use the provided helpers
|
||||||
|
Two animated blueprints are provided: Bevy's Fox, and a Robot I created.
|
||||||
|
|
||||||
|
There is almost no boilerplate, other than setting up ```Bevy_gltf_blueprints```
|
||||||
|
|
||||||
|
If you want to jump straight to controlling the imported animations, jump to :
|
||||||
|
[]('./game/in_game.rs#86')
|
||||||
|
|
||||||
|
## Running this example
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo run --example animation --features bevy/dynamic_linking
|
||||||
|
```
|
5
examples/animation/assets/assets_core.rs
Normal file
5
examples/animation/assets/assets_core.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_asset_loader::prelude::*;
|
||||||
|
|
||||||
|
#[derive(AssetCollection, Resource)]
|
||||||
|
pub struct CoreAssets {}
|
13
examples/animation/assets/assets_game.rs
Normal file
13
examples/animation/assets/assets_game.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
use bevy::gltf::Gltf;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::utils::HashMap;
|
||||||
|
use bevy_asset_loader::prelude::*;
|
||||||
|
|
||||||
|
#[derive(AssetCollection, Resource)]
|
||||||
|
pub struct GameAssets {
|
||||||
|
#[asset(key = "world")]
|
||||||
|
pub world: Handle<Scene>,
|
||||||
|
|
||||||
|
#[asset(key = "models", collection(typed, mapped))]
|
||||||
|
pub models: HashMap<String, Handle<Gltf>>,
|
||||||
|
}
|
35
examples/animation/assets/mod.rs
Normal file
35
examples/animation/assets/mod.rs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
pub mod assets_core;
|
||||||
|
pub use assets_core::*;
|
||||||
|
|
||||||
|
pub mod assets_game;
|
||||||
|
pub use assets_game::*;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_asset_loader::prelude::*;
|
||||||
|
|
||||||
|
use crate::state::AppState;
|
||||||
|
|
||||||
|
pub struct AssetsPlugin;
|
||||||
|
impl Plugin for AssetsPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app
|
||||||
|
// load core assets (ie assets needed in the main menu, and everywhere else before loading more assets in game)
|
||||||
|
.add_loading_state(
|
||||||
|
LoadingState::new(AppState::CoreLoading).continue_to_state(AppState::MenuRunning),
|
||||||
|
)
|
||||||
|
.add_dynamic_collection_to_loading_state::<_, StandardDynamicAssetCollection>(
|
||||||
|
AppState::CoreLoading,
|
||||||
|
"animation/assets_core.assets.ron",
|
||||||
|
)
|
||||||
|
.add_collection_to_loading_state::<_, CoreAssets>(AppState::CoreLoading)
|
||||||
|
// load game assets
|
||||||
|
.add_loading_state(
|
||||||
|
LoadingState::new(AppState::AppLoading).continue_to_state(AppState::AppRunning),
|
||||||
|
)
|
||||||
|
.add_dynamic_collection_to_loading_state::<_, StandardDynamicAssetCollection>(
|
||||||
|
AppState::AppLoading,
|
||||||
|
"animation/assets_game.assets.ron",
|
||||||
|
)
|
||||||
|
.add_collection_to_loading_state::<_, GameAssets>(AppState::AppLoading);
|
||||||
|
}
|
||||||
|
}
|
24
examples/animation/core/camera/camera_replace_proxies.rs
Normal file
24
examples/animation/core/camera/camera_replace_proxies.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings};
|
||||||
|
use bevy::core_pipeline::tonemapping::{DebandDither, Tonemapping};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use super::CameraTrackingOffset;
|
||||||
|
|
||||||
|
pub fn camera_replace_proxies(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut added_cameras: Query<(Entity, &mut Camera), (Added<Camera>, With<CameraTrackingOffset>)>,
|
||||||
|
) {
|
||||||
|
for (entity, mut camera) in added_cameras.iter_mut() {
|
||||||
|
info!("detected added camera, updating proxy");
|
||||||
|
camera.hdr = true;
|
||||||
|
commands
|
||||||
|
.entity(entity)
|
||||||
|
.insert(DebandDither::Enabled)
|
||||||
|
.insert(Tonemapping::BlenderFilmic)
|
||||||
|
.insert(BloomSettings {
|
||||||
|
intensity: 0.01,
|
||||||
|
composite_mode: BloomCompositeMode::Additive,
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
58
examples/animation/core/camera/camera_tracking.rs
Normal file
58
examples/animation/core/camera/camera_tracking.rs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Component for cameras, with an offset from the Trackable target
|
||||||
|
///
|
||||||
|
pub struct CameraTracking {
|
||||||
|
pub offset: Vec3,
|
||||||
|
}
|
||||||
|
impl Default for CameraTracking {
|
||||||
|
fn default() -> Self {
|
||||||
|
CameraTracking {
|
||||||
|
offset: Vec3::new(0.0, 6.0, 8.0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Debug, Deref, DerefMut)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Component for cameras, with an offset from the Trackable target
|
||||||
|
pub struct CameraTrackingOffset(Vec3);
|
||||||
|
impl Default for CameraTrackingOffset {
|
||||||
|
fn default() -> Self {
|
||||||
|
CameraTrackingOffset(Vec3::new(0.0, 6.0, 8.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CameraTrackingOffset {
|
||||||
|
fn new(input: Vec3) -> Self {
|
||||||
|
CameraTrackingOffset(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Add this component to an entity if you want it to be tracked by a Camera
|
||||||
|
pub struct CameraTrackable;
|
||||||
|
|
||||||
|
pub fn camera_track(
|
||||||
|
mut tracking_cameras: Query<
|
||||||
|
(&mut Transform, &CameraTrackingOffset),
|
||||||
|
(
|
||||||
|
With<Camera>,
|
||||||
|
With<CameraTrackingOffset>,
|
||||||
|
Without<CameraTrackable>,
|
||||||
|
),
|
||||||
|
>,
|
||||||
|
camera_tracked: Query<&Transform, With<CameraTrackable>>,
|
||||||
|
) {
|
||||||
|
for (mut camera_transform, tracking_offset) in tracking_cameras.iter_mut() {
|
||||||
|
for tracked_transform in camera_tracked.iter() {
|
||||||
|
let target_position = tracked_transform.translation + tracking_offset.0;
|
||||||
|
let eased_position = camera_transform.translation.lerp(target_position, 0.1);
|
||||||
|
camera_transform.translation = eased_position; // + tracking.offset;// tracked_transform.translation + tracking.offset;
|
||||||
|
*camera_transform = camera_transform.looking_at(tracked_transform.translation, Vec3::Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
examples/animation/core/camera/mod.rs
Normal file
24
examples/animation/core/camera/mod.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
pub mod camera_tracking;
|
||||||
|
pub use camera_tracking::*;
|
||||||
|
|
||||||
|
pub mod camera_replace_proxies;
|
||||||
|
pub use camera_replace_proxies::*;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_gltf_blueprints::GltfBlueprintsSet;
|
||||||
|
|
||||||
|
pub struct CameraPlugin;
|
||||||
|
impl Plugin for CameraPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.register_type::<CameraTrackable>()
|
||||||
|
.register_type::<CameraTracking>()
|
||||||
|
.register_type::<CameraTrackingOffset>()
|
||||||
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
(
|
||||||
|
camera_replace_proxies.after(GltfBlueprintsSet::AfterSpawn),
|
||||||
|
camera_track,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
25
examples/animation/core/lighting/lighting_replace_proxies.rs
Normal file
25
examples/animation/core/lighting/lighting_replace_proxies.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use bevy::pbr::{CascadeShadowConfig, CascadeShadowConfigBuilder};
|
||||||
|
|
||||||
|
// fixme might be too specific to might needs, should it be moved out ? also these are all for lights, not models
|
||||||
|
pub fn lighting_replace_proxies(
|
||||||
|
mut added_dirights: Query<(Entity, &mut DirectionalLight), Added<DirectionalLight>>,
|
||||||
|
mut added_spotlights: Query<&mut SpotLight, Added<SpotLight>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
for (entity, mut light) in added_dirights.iter_mut() {
|
||||||
|
light.illuminance *= 5.0;
|
||||||
|
light.shadows_enabled = true;
|
||||||
|
let shadow_config: CascadeShadowConfig = CascadeShadowConfigBuilder {
|
||||||
|
first_cascade_far_bound: 15.0,
|
||||||
|
maximum_distance: 135.0,
|
||||||
|
..default()
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
commands.entity(entity).insert(shadow_config);
|
||||||
|
}
|
||||||
|
for mut light in added_spotlights.iter_mut() {
|
||||||
|
light.shadows_enabled = true;
|
||||||
|
}
|
||||||
|
}
|
18
examples/animation/core/lighting/mod.rs
Normal file
18
examples/animation/core/lighting/mod.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
mod lighting_replace_proxies;
|
||||||
|
use lighting_replace_proxies::*;
|
||||||
|
|
||||||
|
use bevy::pbr::{DirectionalLightShadowMap, NotShadowCaster};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub struct LightingPlugin;
|
||||||
|
impl Plugin for LightingPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app
|
||||||
|
.insert_resource(DirectionalLightShadowMap { size: 4096 })
|
||||||
|
// FIXME: adding these since they are missing
|
||||||
|
.register_type::<NotShadowCaster>()
|
||||||
|
|
||||||
|
.add_systems(PreUpdate, lighting_replace_proxies) // FIXME: you should actually run this in a specific state most likely
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
29
examples/animation/core/mod.rs
Normal file
29
examples/animation/core/mod.rs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
pub mod camera;
|
||||||
|
use bevy_rapier3d::prelude::Velocity;
|
||||||
|
pub use camera::*;
|
||||||
|
|
||||||
|
pub mod lighting;
|
||||||
|
pub use lighting::*;
|
||||||
|
|
||||||
|
pub mod relationships;
|
||||||
|
pub use relationships::*;
|
||||||
|
|
||||||
|
pub mod physics;
|
||||||
|
pub use physics::*;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_gltf_blueprints::*;
|
||||||
|
|
||||||
|
pub struct CorePlugin;
|
||||||
|
impl Plugin for CorePlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_plugins((
|
||||||
|
LightingPlugin,
|
||||||
|
CameraPlugin,
|
||||||
|
PhysicsPlugin,
|
||||||
|
BlueprintsPlugin {
|
||||||
|
library_folder: "animation/models/library".into(),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
12
examples/animation/core/physics/controls.rs
Normal file
12
examples/animation/core/physics/controls.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use bevy::prelude::{info, ResMut};
|
||||||
|
use bevy_rapier3d::prelude::RapierConfiguration;
|
||||||
|
|
||||||
|
pub fn pause_physics(mut physics_config: ResMut<RapierConfiguration>) {
|
||||||
|
info!("pausing physics");
|
||||||
|
physics_config.physics_pipeline_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resume_physics(mut physics_config: ResMut<RapierConfiguration>) {
|
||||||
|
info!("unpausing physics");
|
||||||
|
physics_config.physics_pipeline_active = true;
|
||||||
|
}
|
37
examples/animation/core/physics/mod.rs
Normal file
37
examples/animation/core/physics/mod.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
pub mod physics_replace_proxies;
|
||||||
|
use bevy_rapier3d::{
|
||||||
|
prelude::{NoUserData, RapierPhysicsPlugin},
|
||||||
|
render::RapierDebugRenderPlugin,
|
||||||
|
};
|
||||||
|
pub use physics_replace_proxies::*;
|
||||||
|
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
|
pub mod controls;
|
||||||
|
pub use controls::*;
|
||||||
|
|
||||||
|
use crate::state::GameState;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
// use super::blueprints::GltfBlueprintsSet;
|
||||||
|
use bevy_gltf_blueprints::GltfBlueprintsSet;
|
||||||
|
// use crate::Collider;
|
||||||
|
pub struct PhysicsPlugin;
|
||||||
|
impl Plugin for PhysicsPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_plugins((
|
||||||
|
RapierPhysicsPlugin::<NoUserData>::default(),
|
||||||
|
RapierDebugRenderPlugin::default(),
|
||||||
|
))
|
||||||
|
.register_type::<AutoAABBCollider>()
|
||||||
|
.register_type::<physics_replace_proxies::Collider>()
|
||||||
|
// find a way to make serde's stuff serializable
|
||||||
|
// .register_type::<bevy_rapier3d::dynamics::CoefficientCombineRule>()
|
||||||
|
//bevy_rapier3d::dynamics::CoefficientCombineRule
|
||||||
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
physics_replace_proxies.after(GltfBlueprintsSet::AfterSpawn),
|
||||||
|
)
|
||||||
|
.add_systems(OnEnter(GameState::InGame), resume_physics)
|
||||||
|
.add_systems(OnExit(GameState::InGame), pause_physics);
|
||||||
|
}
|
||||||
|
}
|
101
examples/animation/core/physics/physics_replace_proxies.rs
Normal file
101
examples/animation/core/physics/physics_replace_proxies.rs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
// use bevy::render::primitives::Aabb;
|
||||||
|
use bevy_rapier3d::geometry::Collider as RapierCollider;
|
||||||
|
use bevy_rapier3d::prelude::{ActiveCollisionTypes, ActiveEvents, ComputedColliderShape};
|
||||||
|
|
||||||
|
use super::utils::*;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub enum Collider {
|
||||||
|
Ball(f32),
|
||||||
|
Cuboid(Vec3),
|
||||||
|
Capsule(Vec3, Vec3, f32),
|
||||||
|
#[default]
|
||||||
|
Mesh,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub enum AutoAABBCollider {
|
||||||
|
#[default]
|
||||||
|
Cuboid,
|
||||||
|
Ball,
|
||||||
|
Capsule,
|
||||||
|
}
|
||||||
|
|
||||||
|
// replaces all physics stand-ins with the actual rapier types
|
||||||
|
pub fn physics_replace_proxies(
|
||||||
|
meshes: Res<Assets<Mesh>>,
|
||||||
|
mesh_handles: Query<&Handle<Mesh>>,
|
||||||
|
mut proxy_colliders: Query<
|
||||||
|
(Entity, &Collider, &Name, &mut Visibility),
|
||||||
|
(Without<RapierCollider>, Added<Collider>),
|
||||||
|
>,
|
||||||
|
// needed for tri meshes
|
||||||
|
children: Query<&Children>,
|
||||||
|
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
for proxy_colider in proxy_colliders.iter_mut() {
|
||||||
|
let (entity, collider_proxy, name, mut visibility) = proxy_colider;
|
||||||
|
// we hide the collider meshes: perhaps they should be removed altogether once processed ?
|
||||||
|
if name.ends_with("_collider") || name.ends_with("_sensor") {
|
||||||
|
*visibility = Visibility::Hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rapier_collider: RapierCollider;
|
||||||
|
match collider_proxy {
|
||||||
|
Collider::Ball(radius) => {
|
||||||
|
info!("generating collider from proxy: ball");
|
||||||
|
rapier_collider = RapierCollider::ball(*radius);
|
||||||
|
commands.entity(entity)
|
||||||
|
.insert(rapier_collider)
|
||||||
|
.insert(ActiveEvents::COLLISION_EVENTS) // FIXME: this is just for demo purposes !!!
|
||||||
|
;
|
||||||
|
}
|
||||||
|
Collider::Cuboid(size) => {
|
||||||
|
info!("generating collider from proxy: cuboid");
|
||||||
|
rapier_collider = RapierCollider::cuboid(size.x, size.y, size.z);
|
||||||
|
commands.entity(entity)
|
||||||
|
.insert(rapier_collider)
|
||||||
|
.insert(ActiveEvents::COLLISION_EVENTS) // FIXME: this is just for demo purposes !!!
|
||||||
|
;
|
||||||
|
}
|
||||||
|
Collider::Capsule(a, b, radius) => {
|
||||||
|
info!("generating collider from proxy: capsule");
|
||||||
|
rapier_collider = RapierCollider::capsule(*a, *b, *radius);
|
||||||
|
commands.entity(entity)
|
||||||
|
.insert(rapier_collider)
|
||||||
|
.insert(ActiveEvents::COLLISION_EVENTS) // FIXME: this is just for demo purposes !!!
|
||||||
|
;
|
||||||
|
}
|
||||||
|
Collider::Mesh => {
|
||||||
|
info!("generating collider from proxy: mesh");
|
||||||
|
for (_, collider_mesh) in
|
||||||
|
Mesh::search_in_children(entity, &children, &meshes, &mesh_handles)
|
||||||
|
{
|
||||||
|
rapier_collider = RapierCollider::from_bevy_mesh(
|
||||||
|
collider_mesh,
|
||||||
|
&ComputedColliderShape::TriMesh,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
commands
|
||||||
|
.entity(entity)
|
||||||
|
.insert(rapier_collider)
|
||||||
|
// FIXME: this is just for demo purposes !!!
|
||||||
|
.insert(
|
||||||
|
ActiveCollisionTypes::default()
|
||||||
|
| ActiveCollisionTypes::KINEMATIC_STATIC
|
||||||
|
| ActiveCollisionTypes::STATIC_STATIC
|
||||||
|
| ActiveCollisionTypes::DYNAMIC_STATIC,
|
||||||
|
)
|
||||||
|
.insert(ActiveEvents::COLLISION_EVENTS);
|
||||||
|
// .insert(ActiveEvents::COLLISION_EVENTS)
|
||||||
|
// break;
|
||||||
|
// RapierCollider::convex_hull(points)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
175
examples/animation/core/physics/utils.rs
Normal file
175
examples/animation/core/physics/utils.rs
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::render::mesh::{MeshVertexAttributeId, PrimitiveTopology, VertexAttributeValues};
|
||||||
|
// TAKEN VERBATIB FROM https://github.com/janhohenheim/foxtrot/blob/src/util/trait_extension.rs
|
||||||
|
|
||||||
|
pub(crate) trait Vec3Ext: Copy {
|
||||||
|
fn is_approx_zero(self) -> bool;
|
||||||
|
fn split(self, up: Vec3) -> SplitVec3;
|
||||||
|
}
|
||||||
|
impl Vec3Ext for Vec3 {
|
||||||
|
#[inline]
|
||||||
|
fn is_approx_zero(self) -> bool {
|
||||||
|
self.length_squared() < 1e-5
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn split(self, up: Vec3) -> SplitVec3 {
|
||||||
|
let vertical = up * self.dot(up);
|
||||||
|
let horizontal = self - vertical;
|
||||||
|
SplitVec3 {
|
||||||
|
vertical,
|
||||||
|
horizontal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub(crate) struct SplitVec3 {
|
||||||
|
pub(crate) vertical: Vec3,
|
||||||
|
pub(crate) horizontal: Vec3,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait Vec2Ext: Copy {
|
||||||
|
fn is_approx_zero(self) -> bool;
|
||||||
|
fn x0y(self) -> Vec3;
|
||||||
|
}
|
||||||
|
impl Vec2Ext for Vec2 {
|
||||||
|
#[inline]
|
||||||
|
fn is_approx_zero(self) -> bool {
|
||||||
|
self.length_squared() < 1e-5
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn x0y(self) -> Vec3 {
|
||||||
|
Vec3::new(self.x, 0., self.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait MeshExt {
|
||||||
|
fn transform(&mut self, transform: Transform);
|
||||||
|
fn transformed(&self, transform: Transform) -> Mesh;
|
||||||
|
fn read_coords_mut(&mut self, id: impl Into<MeshVertexAttributeId>) -> &mut Vec<[f32; 3]>;
|
||||||
|
fn search_in_children<'a>(
|
||||||
|
parent: Entity,
|
||||||
|
children: &'a Query<&Children>,
|
||||||
|
meshes: &'a Assets<Mesh>,
|
||||||
|
mesh_handles: &'a Query<&Handle<Mesh>>,
|
||||||
|
) -> Vec<(Entity, &'a Mesh)>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MeshExt for Mesh {
|
||||||
|
fn transform(&mut self, transform: Transform) {
|
||||||
|
for coords in self.read_coords_mut(Mesh::ATTRIBUTE_POSITION.clone()) {
|
||||||
|
let vec3 = (*coords).into();
|
||||||
|
let transformed = transform.transform_point(vec3);
|
||||||
|
*coords = transformed.into();
|
||||||
|
}
|
||||||
|
for normal in self.read_coords_mut(Mesh::ATTRIBUTE_NORMAL.clone()) {
|
||||||
|
let vec3 = (*normal).into();
|
||||||
|
let transformed = transform.rotation.mul_vec3(vec3);
|
||||||
|
*normal = transformed.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transformed(&self, transform: Transform) -> Mesh {
|
||||||
|
let mut mesh = self.clone();
|
||||||
|
mesh.transform(transform);
|
||||||
|
mesh
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_coords_mut(&mut self, id: impl Into<MeshVertexAttributeId>) -> &mut Vec<[f32; 3]> {
|
||||||
|
// Guaranteed by Bevy for the current usage
|
||||||
|
match self
|
||||||
|
.attribute_mut(id)
|
||||||
|
.expect("Failed to read unknown mesh attribute")
|
||||||
|
{
|
||||||
|
VertexAttributeValues::Float32x3(values) => values,
|
||||||
|
// Guaranteed by Bevy for the current usage
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_in_children<'a>(
|
||||||
|
parent: Entity,
|
||||||
|
children_query: &'a Query<&Children>,
|
||||||
|
meshes: &'a Assets<Mesh>,
|
||||||
|
mesh_handles: &'a Query<&Handle<Mesh>>,
|
||||||
|
) -> Vec<(Entity, &'a Mesh)> {
|
||||||
|
if let Ok(children) = children_query.get(parent) {
|
||||||
|
let mut result: Vec<_> = children
|
||||||
|
.iter()
|
||||||
|
.filter_map(|entity| mesh_handles.get(*entity).ok().map(|mesh| (*entity, mesh)))
|
||||||
|
.map(|(entity, mesh_handle)| {
|
||||||
|
(
|
||||||
|
entity,
|
||||||
|
meshes
|
||||||
|
.get(mesh_handle)
|
||||||
|
.expect("Failed to get mesh from handle"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map(|(entity, mesh)| {
|
||||||
|
assert_eq!(mesh.primitive_topology(), PrimitiveTopology::TriangleList);
|
||||||
|
(entity, mesh)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let mut inner_result = children
|
||||||
|
.iter()
|
||||||
|
.flat_map(|entity| {
|
||||||
|
Self::search_in_children(*entity, children_query, meshes, mesh_handles)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
result.append(&mut inner_result);
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait F32Ext: Copy {
|
||||||
|
fn is_approx_zero(self) -> bool;
|
||||||
|
fn squared(self) -> f32;
|
||||||
|
fn lerp(self, other: f32, ratio: f32) -> f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl F32Ext for f32 {
|
||||||
|
#[inline]
|
||||||
|
fn is_approx_zero(self) -> bool {
|
||||||
|
self.abs() < 1e-5
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn squared(self) -> f32 {
|
||||||
|
self * self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn lerp(self, other: f32, ratio: f32) -> f32 {
|
||||||
|
self.mul_add(1. - ratio, other * ratio)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait TransformExt: Copy {
|
||||||
|
fn horizontally_looking_at(self, target: Vec3, up: Vec3) -> Transform;
|
||||||
|
fn lerp(self, other: Transform, ratio: f32) -> Transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransformExt for Transform {
|
||||||
|
fn horizontally_looking_at(self, target: Vec3, up: Vec3) -> Transform {
|
||||||
|
let direction = target - self.translation;
|
||||||
|
let horizontal_direction = direction - up * direction.dot(up);
|
||||||
|
let look_target = self.translation + horizontal_direction;
|
||||||
|
self.looking_at(look_target, up)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lerp(self, other: Transform, ratio: f32) -> Transform {
|
||||||
|
let translation = self.translation.lerp(other.translation, ratio);
|
||||||
|
let rotation = self.rotation.slerp(other.rotation, ratio);
|
||||||
|
let scale = self.scale.lerp(other.scale, ratio);
|
||||||
|
Transform {
|
||||||
|
translation,
|
||||||
|
rotation,
|
||||||
|
scale,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
examples/animation/core/physics/utils_old.rs
Normal file
75
examples/animation/core/physics/utils_old.rs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::render::mesh::{MeshVertexAttributeId, PrimitiveTopology, VertexAttributeValues};
|
||||||
|
// TAKEN VERBATIB FROM https://github.com/janhohenheim/foxtrot/blob/6e31fc02652fc9d085a4adde0a73ab007dbbb0dc/src/util/trait_extension.rs
|
||||||
|
|
||||||
|
pub trait Vec3Ext {
|
||||||
|
#[allow(clippy::wrong_self_convention)] // Because [`Vec3`] is [`Copy`]
|
||||||
|
fn is_approx_zero(self) -> bool;
|
||||||
|
fn x0z(self) -> Vec3;
|
||||||
|
}
|
||||||
|
impl Vec3Ext for Vec3 {
|
||||||
|
fn is_approx_zero(self) -> bool {
|
||||||
|
[self.x, self.y, self.z].iter().all(|&x| x.abs() < 1e-5)
|
||||||
|
}
|
||||||
|
fn x0z(self) -> Vec3 {
|
||||||
|
Vec3::new(self.x, 0., self.z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MeshExt {
|
||||||
|
fn transform(&mut self, transform: Transform);
|
||||||
|
fn transformed(&self, transform: Transform) -> Mesh;
|
||||||
|
fn read_coords_mut(&mut self, id: impl Into<MeshVertexAttributeId>) -> &mut Vec<[f32; 3]>;
|
||||||
|
fn search_in_children<'a>(
|
||||||
|
children: &'a Children,
|
||||||
|
meshes: &'a Assets<Mesh>,
|
||||||
|
mesh_handles: &'a Query<&Handle<Mesh>>,
|
||||||
|
) -> (Entity, &'a Mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MeshExt for Mesh {
|
||||||
|
fn transform(&mut self, transform: Transform) {
|
||||||
|
for attribute in [Mesh::ATTRIBUTE_POSITION, Mesh::ATTRIBUTE_NORMAL] {
|
||||||
|
for coords in self.read_coords_mut(attribute.clone()) {
|
||||||
|
let vec3 = (*coords).into();
|
||||||
|
let transformed = transform.transform_point(vec3);
|
||||||
|
*coords = transformed.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transformed(&self, transform: Transform) -> Mesh {
|
||||||
|
let mut mesh = self.clone();
|
||||||
|
mesh.transform(transform);
|
||||||
|
mesh
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_coords_mut(&mut self, id: impl Into<MeshVertexAttributeId>) -> &mut Vec<[f32; 3]> {
|
||||||
|
match self.attribute_mut(id).unwrap() {
|
||||||
|
VertexAttributeValues::Float32x3(values) => values,
|
||||||
|
// Guaranteed by Bevy
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_in_children<'a>(
|
||||||
|
children: &'a Children,
|
||||||
|
meshes: &'a Assets<Mesh>,
|
||||||
|
mesh_handles: &'a Query<&Handle<Mesh>>,
|
||||||
|
) -> (Entity, &'a Mesh) {
|
||||||
|
let entity_handles: Vec<_> = children
|
||||||
|
.iter()
|
||||||
|
.filter_map(|entity| mesh_handles.get(*entity).ok().map(|mesh| (*entity, mesh)))
|
||||||
|
.collect();
|
||||||
|
assert_eq!(
|
||||||
|
entity_handles.len(),
|
||||||
|
1,
|
||||||
|
"Collider must contain exactly one mesh, but found {}",
|
||||||
|
entity_handles.len()
|
||||||
|
);
|
||||||
|
let (entity, mesh_handle) = entity_handles.first().unwrap();
|
||||||
|
let mesh = meshes.get(mesh_handle).unwrap();
|
||||||
|
assert_eq!(mesh.primitive_topology(), PrimitiveTopology::TriangleList);
|
||||||
|
(*entity, mesh)
|
||||||
|
}
|
||||||
|
}
|
11
examples/animation/core/relationships/mod.rs
Normal file
11
examples/animation/core/relationships/mod.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
pub mod relationships_insert_dependant_components;
|
||||||
|
pub use relationships_insert_dependant_components::*;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub struct EcsRelationshipsPlugin;
|
||||||
|
impl Plugin for EcsRelationshipsPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub fn insert_dependant_component<
|
||||||
|
Dependant: Component,
|
||||||
|
Dependency: Component + std::default::Default,
|
||||||
|
>(
|
||||||
|
mut commands: Commands,
|
||||||
|
entities_without_depency: Query<(Entity, &Name), (With<Dependant>, Without<Dependency>)>,
|
||||||
|
) {
|
||||||
|
for (entity, name) in entities_without_depency.iter() {
|
||||||
|
let name = name.clone().to_string();
|
||||||
|
commands.entity(entity).insert(Dependency::default());
|
||||||
|
warn!("found an entity called {} with a {} component but without an {}, please check your assets", name.clone(), std::any::type_name::<Dependant>(), std::any::type_name::<Dependency>());
|
||||||
|
}
|
||||||
|
}
|
302
examples/animation/game/in_game.rs
Normal file
302
examples/animation/game/in_game.rs
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
use bevy_rapier3d::prelude::Velocity;
|
||||||
|
use rand::Rng;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
assets::GameAssets,
|
||||||
|
state::{GameState, InAppRunning},
|
||||||
|
};
|
||||||
|
use bevy_gltf_blueprints::{
|
||||||
|
AnimationPlayerLink, Animations, BluePrintBundle, BlueprintName, GameWorldTag,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{Fox, Player, Robot};
|
||||||
|
|
||||||
|
pub fn setup_game(
|
||||||
|
mut commands: Commands,
|
||||||
|
game_assets: Res<GameAssets>,
|
||||||
|
mut next_game_state: ResMut<NextState<GameState>>,
|
||||||
|
) {
|
||||||
|
println!("setting up all stuff");
|
||||||
|
commands.insert_resource(AmbientLight {
|
||||||
|
color: Color::WHITE,
|
||||||
|
brightness: 0.2,
|
||||||
|
});
|
||||||
|
// here we actually spawn our game world/level
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
SceneBundle {
|
||||||
|
scene: game_assets.world.clone(),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
bevy::prelude::Name::from("world"),
|
||||||
|
GameWorldTag,
|
||||||
|
InAppRunning,
|
||||||
|
));
|
||||||
|
|
||||||
|
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 = 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()),
|
||||||
|
transform: TransformBundle::from_transform(Transform::from_xyz(x, 0.0, y)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
bevy::prelude::Name::from(format!("Spawned{}", name_index)),
|
||||||
|
// BlueprintName("Health_Pickup".to_string()),
|
||||||
|
// SpawnHere,
|
||||||
|
// TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)),
|
||||||
|
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, &AnimationPlayerLink, &Animations), 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 distance >= 8.5 && distance < 10.0 {
|
||||||
|
anim_name = "Walk";
|
||||||
|
} else if distance >= 10.0 && distance < 15.0 {
|
||||||
|
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, &AnimationPlayerLink, &Animations), 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 distance >= 8.5 && distance < 10.0 {
|
||||||
|
anim_name = "Scan";
|
||||||
|
} else if distance >= 10.0 && distance < 15.0 {
|
||||||
|
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<(&AnimationPlayerLink, &Animations), With<Robot>>,
|
||||||
|
animated_foxes: Query<(&AnimationPlayerLink, &Animations), With<Fox>>,
|
||||||
|
|
||||||
|
mut animation_players: Query<&mut AnimationPlayer>,
|
||||||
|
|
||||||
|
keycode: Res<Input<KeyCode>>,
|
||||||
|
// mut entities_with_animations : Query<(&mut AnimationPlayer, &mut Animations)>,
|
||||||
|
) {
|
||||||
|
// robots
|
||||||
|
if keycode.just_pressed(KeyCode::B) {
|
||||||
|
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::W) {
|
||||||
|
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::X) {
|
||||||
|
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::C) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
111
examples/animation/game/in_main_menu.rs
Normal file
111
examples/animation/game/in_main_menu.rs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::state::{AppState, GameState, InMainMenu};
|
||||||
|
|
||||||
|
pub fn setup_main_menu(mut commands: Commands) {
|
||||||
|
commands.spawn((Camera2dBundle::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<Input<KeyCode>>,
|
||||||
|
|
||||||
|
mut next_app_state: ResMut<NextState<AppState>>,
|
||||||
|
// mut next_game_state: ResMut<NextState<GameState>>,
|
||||||
|
) {
|
||||||
|
if keycode.just_pressed(KeyCode::Return) {
|
||||||
|
next_app_state.set(AppState::AppLoading);
|
||||||
|
// next_game_state.set(GameState::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
if keycode.just_pressed(KeyCode::L) {
|
||||||
|
next_app_state.set(AppState::AppLoading);
|
||||||
|
// load_requested_events.send(LoadRequest { path: "toto".into() })
|
||||||
|
}
|
||||||
|
|
||||||
|
if keycode.just_pressed(KeyCode::S) {
|
||||||
|
// save_requested_events.send(SaveRequest { path: "toto".into() })
|
||||||
|
}
|
||||||
|
}
|
128
examples/animation/game/mod.rs
Normal file
128
examples/animation/game/mod.rs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
pub mod in_game;
|
||||||
|
pub use in_game::*;
|
||||||
|
|
||||||
|
pub mod in_main_menu;
|
||||||
|
pub use in_main_menu::*;
|
||||||
|
|
||||||
|
pub mod picking;
|
||||||
|
pub use picking::*;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
insert_dependant_component,
|
||||||
|
state::{AppState, GameState},
|
||||||
|
};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_rapier3d::prelude::*;
|
||||||
|
|
||||||
|
// this file is just for demo purposes, contains various types of components, systems etc
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub enum SoundMaterial {
|
||||||
|
Metal,
|
||||||
|
Wood,
|
||||||
|
Rock,
|
||||||
|
Cloth,
|
||||||
|
Squishy,
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Demo marker component
|
||||||
|
pub struct Player;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Demo component showing auto injection of components
|
||||||
|
pub struct ShouldBeWithPlayer;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// Demo marker component
|
||||||
|
pub struct Interactible;
|
||||||
|
|
||||||
|
#[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;
|
||||||
|
|
||||||
|
fn player_move_demo(
|
||||||
|
keycode: Res<Input<KeyCode>>,
|
||||||
|
mut players: Query<&mut Transform, With<Player>>,
|
||||||
|
) {
|
||||||
|
let speed = 0.2;
|
||||||
|
if let Ok(mut player) = players.get_single_mut() {
|
||||||
|
if keycode.pressed(KeyCode::Left) {
|
||||||
|
player.translation.x += speed;
|
||||||
|
}
|
||||||
|
if keycode.pressed(KeyCode::Right) {
|
||||||
|
player.translation.x -= speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if keycode.pressed(KeyCode::Up) {
|
||||||
|
player.translation.z += speed;
|
||||||
|
}
|
||||||
|
if keycode.pressed(KeyCode::Down) {
|
||||||
|
player.translation.z -= speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// collision tests/debug
|
||||||
|
pub fn test_collision_events(
|
||||||
|
mut collision_events: EventReader<CollisionEvent>,
|
||||||
|
mut contact_force_events: EventReader<ContactForceEvent>,
|
||||||
|
) {
|
||||||
|
for collision_event in collision_events.iter() {
|
||||||
|
println!("collision");
|
||||||
|
match collision_event {
|
||||||
|
CollisionEvent::Started(_entity1, _entity2, _) => {
|
||||||
|
println!("collision started")
|
||||||
|
}
|
||||||
|
CollisionEvent::Stopped(_entity1, _entity2, _) => {
|
||||||
|
println!("collision ended")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for contact_force_event in contact_force_events.iter() {
|
||||||
|
println!("Received contact force event: {:?}", contact_force_event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GamePlugin;
|
||||||
|
impl Plugin for GamePlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_plugins(PickingPlugin)
|
||||||
|
.register_type::<Interactible>()
|
||||||
|
.register_type::<SoundMaterial>()
|
||||||
|
.register_type::<Player>()
|
||||||
|
.register_type::<Robot>()
|
||||||
|
.register_type::<Fox>()
|
||||||
|
// little helper utility, to automatically inject components that are dependant on an other component
|
||||||
|
// ie, here an Entity with a Player component should also always have a ShouldBeWithPlayer component
|
||||||
|
// you get a warning if you use this, as I consider this to be stop-gap solution (usually you should have either a bundle, or directly define all needed components)
|
||||||
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
(
|
||||||
|
player_move_demo,
|
||||||
|
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))
|
||||||
|
.add_systems(OnEnter(AppState::AppRunning), setup_game);
|
||||||
|
}
|
||||||
|
}
|
37
examples/animation/game/picking.rs
Normal file
37
examples/animation/game/picking.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
use super::Player;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct Pickable;
|
||||||
|
|
||||||
|
// very simple, crude picking (as in picking up objects) implementation
|
||||||
|
|
||||||
|
pub fn picking(
|
||||||
|
players: Query<&GlobalTransform, With<Player>>,
|
||||||
|
pickables: Query<(Entity, &GlobalTransform), With<Pickable>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
for player_transforms in players.iter() {
|
||||||
|
for (pickable, pickable_transforms) in pickables.iter() {
|
||||||
|
let distance = player_transforms
|
||||||
|
.translation()
|
||||||
|
.distance(pickable_transforms.translation());
|
||||||
|
if distance < 2.5 {
|
||||||
|
commands.entity(pickable).despawn_recursive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PickingPlugin;
|
||||||
|
impl Plugin for PickingPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.register_type::<Pickable>().add_systems(
|
||||||
|
Update,
|
||||||
|
(
|
||||||
|
picking, //.run_if(in_state(AppState::Running)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
39
examples/animation/main.rs
Normal file
39
examples/animation/main.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use bevy::{asset::ChangeWatcher, prelude::*};
|
||||||
|
use bevy_editor_pls::prelude::*;
|
||||||
|
|
||||||
|
mod core;
|
||||||
|
use crate::core::*;
|
||||||
|
|
||||||
|
pub mod assets;
|
||||||
|
use assets::*;
|
||||||
|
|
||||||
|
pub mod state;
|
||||||
|
use state::*;
|
||||||
|
|
||||||
|
mod game;
|
||||||
|
use game::*;
|
||||||
|
|
||||||
|
mod test_components;
|
||||||
|
use test_components::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins((
|
||||||
|
DefaultPlugins.set(AssetPlugin {
|
||||||
|
// 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
|
||||||
|
EditorPlugin::default(),
|
||||||
|
// our custom plugins
|
||||||
|
StatePlugin,
|
||||||
|
AssetsPlugin,
|
||||||
|
CorePlugin, // reusable plugins
|
||||||
|
GamePlugin, // specific to our game
|
||||||
|
ComponentsTestPlugin, // Showcases different type of components /structs
|
||||||
|
))
|
||||||
|
.run();
|
||||||
|
}
|
54
examples/animation/state.rs
Normal file
54
examples/animation/state.rs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
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>();
|
||||||
|
}
|
||||||
|
}
|
80
examples/animation/test_components.rs
Normal file
80
examples/animation/test_components.rs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct UnitTest;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleTestF32(f32);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleTestU64(u64);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct TuppleTestStr(String);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleTest2(f32, u64, String);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleTestBool(bool);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleVec2(Vec2);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleVec3(Vec3);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleVec(Vec<String>);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct TuppleTestColor(Color);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
struct BasicTest {
|
||||||
|
a: f32,
|
||||||
|
b: u64,
|
||||||
|
c: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub enum EnumTest {
|
||||||
|
Metal,
|
||||||
|
Wood,
|
||||||
|
Rock,
|
||||||
|
Cloth,
|
||||||
|
Squishy,
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ComponentsTestPlugin;
|
||||||
|
impl Plugin for ComponentsTestPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.register_type::<BasicTest>()
|
||||||
|
.register_type::<UnitTest>()
|
||||||
|
.register_type::<TuppleTestF32>()
|
||||||
|
.register_type::<TuppleTestU64>()
|
||||||
|
.register_type::<TuppleTestStr>()
|
||||||
|
.register_type::<TuppleTestBool>()
|
||||||
|
.register_type::<TuppleTest2>()
|
||||||
|
.register_type::<TuppleVec2>()
|
||||||
|
.register_type::<TuppleVec3>()
|
||||||
|
.register_type::<EnumTest>()
|
||||||
|
.register_type::<TuppleTestColor>()
|
||||||
|
.register_type::<TuppleVec>()
|
||||||
|
.register_type::<Vec<String>>();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user