Compare commits

...

2 Commits

Author SHA1 Message Date
kaosat.dev
ce473a357e feat(animation): split animation logic/components into Blueprint vs Instance animations
* renamed existing animation components with Blueprint prefix
 * added almost identical but seperate InstanceAnimations & InstanceAnimationPlayerLink
2024-03-22 13:59:01 +01:00
kaosat.dev
27201e5cd6 feat():
* sucessfull experiment with ways to animate world level objects, using
the same logic as the blueprint animations
 * made required changes
2024-03-22 11:11:45 +01:00
7 changed files with 259 additions and 51 deletions

View File

@ -4,7 +4,7 @@ use bevy::utils::HashMap;
#[derive(Component, Reflect, Default, Debug)] #[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)] #[reflect(Component)]
/// storage for animations for a given entity (hierarchy), essentially a clone of gltf's `named_animations` /// storage for animations for a given entity (hierarchy), essentially a clone of gltf's `named_animations`
pub struct Animations { pub struct BlueprintAnimations {
pub named_animations: HashMap<String, Handle<AnimationClip>>, pub named_animations: HashMap<String, Handle<AnimationClip>>,
} }
@ -13,10 +13,25 @@ pub struct Animations {
/// so that the root entity knows which of its children contains an actualy `AnimationPlayer` component /// 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" /// 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 /// ie armature/root for animated models, which means more complex queries to trigger animations that we want to avoid
pub struct AnimationPlayerLink(pub Entity); pub struct BlueprintAnimationPlayerLink(pub Entity);
#[derive(Component, Reflect, Default, Debug)] #[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)] #[reflect(Component)]
pub struct Animated{ pub struct Animated{
pub animations: Vec<String> pub animations: Vec<String>
} }
#[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 InstanceAnimations {
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 InstanceAnimationPlayerLink(pub Entity);

View File

@ -119,8 +119,11 @@ impl Plugin for BlueprintsPlugin {
.register_type::<BlueprintName>() .register_type::<BlueprintName>()
.register_type::<MaterialInfo>() .register_type::<MaterialInfo>()
.register_type::<SpawnHere>() .register_type::<SpawnHere>()
.register_type::<Animations>()
.register_type::<BlueprintAnimations>()
.register_type::<InstanceAnimations>()
.register_type::<Animated>() .register_type::<Animated>()
.register_type::<BlueprintsList>() .register_type::<BlueprintsList>()
.register_type::<Vec<String>>() .register_type::<Vec<String>>()
.register_type::<HashMap<String, Vec<String>>>() .register_type::<HashMap<String, Vec<String>>>()

View File

@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
use bevy::{gltf::Gltf, prelude::*, utils::HashMap}; use bevy::{gltf::Gltf, prelude::*, utils::HashMap};
use crate::{Animations, BluePrintsConfig}; use crate::{BlueprintAnimations, 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)]
@ -279,13 +279,13 @@ pub(crate) fn spawn_from_blueprints(
transform: transforms, transform: transforms,
..Default::default() ..Default::default()
}, },
Animations {
named_animations: gltf.named_animations.clone(),
},
Spawned, Spawned,
OriginalChildren(original_children), 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() { if add_to_world.is_some() {
let world = game_world let world = game_world
.get_single_mut() .get_single_mut()

View File

@ -5,7 +5,7 @@ use bevy::prelude::*;
use bevy::scene::SceneInstance; use bevy::scene::SceneInstance;
// use bevy::utils::hashbrown::HashSet; // use bevy::utils::hashbrown::HashSet;
use super::{AnimationPlayerLink, Animations}; use super::{BlueprintAnimationPlayerLink, BlueprintAnimations};
use super::{SpawnHere, Spawned}; use super::{SpawnHere, Spawned};
use crate::{ use crate::{
AssetsToLoad, BlueprintAssetsLoaded, CopyComponents, InBlueprint, NoInBlueprint, AssetsToLoad, BlueprintAssetsLoaded, CopyComponents, InBlueprint, NoInBlueprint,
@ -24,7 +24,7 @@ pub(crate) fn spawned_blueprint_post_process(
Entity, Entity,
&Children, &Children,
&OriginalChildren, &OriginalChildren,
&Animations, &BlueprintAnimations,
Option<&NoInBlueprint>, Option<&NoInBlueprint>,
Option<&Name>, Option<&Name>,
), ),
@ -85,7 +85,7 @@ pub(crate) fn spawned_blueprint_post_process(
// FIXME: stopgap solution: since we cannot use an AnimationPlayer at the root entity level // 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, // 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 // BUT we still want to have some marker/control at the root entity level, we add this
commands.entity(original).insert(AnimationPlayerLink(added)); commands.entity(original).insert(BlueprintAnimationPlayerLink(added));
} }
} }
} }

View File

@ -2947,6 +2947,39 @@
"type": "object", "type": "object",
"typeInfo": "Struct" "typeInfo": "Struct"
}, },
"bevy_example::game::Marker1": {
"additionalProperties": false,
"isComponent": true,
"isResource": false,
"properties": {},
"required": [],
"short_name": "Marker1",
"title": "bevy_example::game::Marker1",
"type": "object",
"typeInfo": "Struct"
},
"bevy_example::game::Marker2": {
"additionalProperties": false,
"isComponent": true,
"isResource": false,
"properties": {},
"required": [],
"short_name": "Marker2",
"title": "bevy_example::game::Marker2",
"type": "object",
"typeInfo": "Struct"
},
"bevy_example::game::Marker3": {
"additionalProperties": false,
"isComponent": true,
"isResource": false,
"properties": {},
"required": [],
"short_name": "Marker3",
"title": "bevy_example::game::Marker3",
"type": "object",
"typeInfo": "Struct"
},
"bevy_example::test_components::AComponentWithAnExtremlyExageratedOrMaybeNotButCouldBeNameOrWut": { "bevy_example::test_components::AComponentWithAnExtremlyExageratedOrMaybeNotButCouldBeNameOrWut": {
"additionalProperties": false, "additionalProperties": false,
"isComponent": true, "isComponent": true,
@ -3535,7 +3568,7 @@
"type": "object", "type": "object",
"typeInfo": "Struct" "typeInfo": "Struct"
}, },
"bevy_gltf_blueprints::animation::Animations": { "bevy_gltf_blueprints::animation::BlueprintAnimations": {
"additionalProperties": false, "additionalProperties": false,
"isComponent": true, "isComponent": true,
"isResource": false, "isResource": false,
@ -3549,8 +3582,27 @@
"required": [ "required": [
"named_animations" "named_animations"
], ],
"short_name": "Animations", "short_name": "BlueprintAnimations",
"title": "bevy_gltf_blueprints::animation::Animations", "title": "bevy_gltf_blueprints::animation::BlueprintAnimations",
"type": "object",
"typeInfo": "Struct"
},
"bevy_gltf_blueprints::animation::InstanceAnimations": {
"additionalProperties": false,
"isComponent": true,
"isResource": false,
"properties": {
"named_animations": {
"type": {
"$ref": "#/$defs/bevy_utils::hashbrown::HashMap<alloc::string::String, bevy_asset::handle::Handle<bevy_animation::AnimationClip>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>"
}
}
},
"required": [
"named_animations"
],
"short_name": "InstanceAnimations",
"title": "bevy_gltf_blueprints::animation::InstanceAnimations",
"type": "object", "type": "object",
"typeInfo": "Struct" "typeInfo": "Struct"
}, },

View File

@ -3,7 +3,7 @@ use std::{
collections::HashMap, fs, time::Duration collections::HashMap, fs, time::Duration
}; };
use bevy_gltf_blueprints::{Animated, AnimationPlayerLink, Animations, BlueprintName, BlueprintsList}; use bevy_gltf_blueprints::{Animated, BlueprintAnimationPlayerLink, BlueprintAnimations, InstanceAnimationPlayerLink, InstanceAnimations, BlueprintName, BlueprintsList, GltfBlueprintsSet};
pub use in_game::*; pub use in_game::*;
use bevy::{ use bevy::{
@ -29,7 +29,7 @@ fn validate_export(
children: Query<&Children>, children: Query<&Children>,
names: Query<&Name>, names: Query<&Name>,
blueprints: Query<(Entity, &Name, &BlueprintName)>, blueprints: Query<(Entity, &Name, &BlueprintName)>,
animation_player_links: Query<(Entity, &AnimationPlayerLink)>, animation_player_links: Query<(Entity, &BlueprintAnimationPlayerLink)>,
empties_candidates: Query<(Entity, &Name, &GlobalTransform)>, empties_candidates: Query<(Entity, &Name, &GlobalTransform)>,
blueprints_list: Query<(Entity, &BlueprintsList)>, blueprints_list: Query<(Entity, &BlueprintsList)>,
@ -138,22 +138,23 @@ fn setup_main_scene_animations(
commands.insert_resource(AnimTest(asset_server.load("models/World.glb"))); commands.insert_resource(AnimTest(asset_server.load("models/World.glb")));
} }
fn animations(
foo:Query<(Entity, &Name, &AnimationPlayer),(Added<AnimationPlayer>)>,
bla:Query<(Entity, &Name, &Animated),(Added<Animated>, Without<AnimationPlayerLink>)>,
blurp: Res<AnimTest>,
asset_server: Res<AssetServer>, fn animations(
added_animation_players:Query<(Entity, &Name, &AnimationPlayer)>,
addded_animateds:Query<(Entity, &Name, &Animated),(Added<Animated>)>,
animtest: Res<AnimTest>,
mut commands: Commands, mut commands: Commands,
assets_gltf: Res<Assets<Gltf>>, assets_gltf: Res<Assets<Gltf>>,
) { parents: Query<&Parent>,
names: Query<&Name>,
) {
for (entity, name, animated) in bla.iter() { for (entity, name, animated) in addded_animateds.iter() {
// println!("animated stuf {:?} on entity {}", animated, name); // println!("animated stuf {:?} on entity {}", animated, name);
let gltf = assets_gltf.get(&animtest.0).unwrap();
let gltf = assets_gltf.get(&blurp.0).unwrap();
let animations_list = animated; let animations_list = animated;
let mut matching_data = true; let mut matching_data = true;
@ -166,43 +167,180 @@ fn animations(
if matching_data { if matching_data {
println!("inserting Animations components into {} ({:?})", name, entity); println!("inserting Animations components into {} ({:?})", name, entity);
println!("Found match {:?}", gltf.named_animations); println!("Found match {:?}", gltf.named_animations);
commands.entity(entity).remove::<Animations>(); // commands.entity(entity).remove::<Animations>();
commands.entity(entity).insert(( // FIXME: for some reason this does NOT overwrite the component ??
Animations {
named_animations: gltf.named_animations.clone() commands.entity(entity).insert(
InstanceAnimations {
named_animations: gltf.named_animations.clone(),
},
);
//animations.named_animations = gltf.named_animations.clone();
for ancestor in parents.iter_ancestors(entity) {
if added_animation_players.contains(ancestor) {
println!("found match with animationPlayer !! {:?}",names.get(ancestor));
commands.entity(entity).insert(InstanceAnimationPlayerLink(ancestor));
} }
)); // info!("{:?} is an ancestor of {:?}", ancestor, player);
}
} }
} }
/*for bla in foo.iter() {
let mut counter = 0;
counter +=1;
println!("found some animations {} {}", counter, bla.1);
if bla.1.to_string() == "Collection".to_string(){
/*commands.insert_resource(Animations(vec![
asset_server.load("models/World.glb#Animation0"),
asset_server.load("models/World.glb#Animation1"),
]));*/
/*commands.entity(bla.0).insert(Animations {
named_animations:
})*/
}
}*/
} }
fn play_animations(
animated_marker1: Query<(&InstanceAnimationPlayerLink, &InstanceAnimations), (With<Animated>, With<Marker1>)>,
animated_marker2: Query<(&InstanceAnimationPlayerLink, &InstanceAnimations), (With<Animated>, With<Marker2>)>,
animated_marker3: Query<(&InstanceAnimationPlayerLink, &InstanceAnimations, &BlueprintAnimationPlayerLink, &BlueprintAnimations), (With<Animated>, With<Marker3>)>,
mut animation_players: Query<&mut AnimationPlayer>,
keycode: Res<ButtonInput<KeyCode>>,
) {
if keycode.just_pressed(KeyCode::KeyM) {
for (link, animations) in animated_marker1.iter() {
println!("animations {:?}", animations.named_animations);
let mut animation_player = animation_players.get_mut(link.0).unwrap();
let anim_name = "Blueprint1_move";
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::KeyJ) {
for (link, animations) in animated_marker1.iter() {
println!("animations {:?}", animations.named_animations);
let mut animation_player = animation_players.get_mut(link.0).unwrap();
let anim_name = "Blueprint1_jump";
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::KeyA) {
for (link, animations) in animated_marker2.iter() {
println!("animations {:?}", animations.named_animations);
let mut animation_player = animation_players.get_mut(link.0).unwrap();
let anim_name = "Blueprint1_move";
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::KeyB) {
for (link, animations) in animated_marker2.iter() {
println!("animations {:?}", animations.named_animations);
let mut animation_player = animation_players.get_mut(link.0).unwrap();
let anim_name = "Blueprint1_jump";
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();
}
}
// play instance animation
if keycode.just_pressed(KeyCode::KeyW) {
for (link, animations, _, _) in animated_marker3.iter() {
println!("animations {:?}", animations.named_animations);
let mut animation_player = animation_players.get_mut(link.0).unwrap();
let anim_name = "Blueprint8_move";
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();
}
}
// play blueprint animation
if keycode.just_pressed(KeyCode::KeyX) {
for (_, _, link, animations) in animated_marker3.iter() {
println!("animations {:?}", animations.named_animations);
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();
}
}
}
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// flag component for testing
pub struct Marker1;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// flag component for testing
pub struct Marker2;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// flag component for testing
pub struct Marker3;
pub struct GamePlugin; pub struct GamePlugin;
impl Plugin for GamePlugin { impl Plugin for GamePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Update, (spawn_test).run_if(in_state(GameState::InGame))) app.register_type::<Marker1>()
.register_type::<Marker2>()
.register_type::<Marker3>()
.add_systems(Update, (spawn_test).run_if(in_state(GameState::InGame)))
.add_systems(Update, validate_export) .add_systems(Update, validate_export)
.add_systems(OnEnter(AppState::MenuRunning), start_game) .add_systems(OnEnter(AppState::MenuRunning), start_game)
.add_systems(OnEnter(AppState::AppRunning), setup_game) .add_systems(OnEnter(AppState::AppRunning), setup_game)
.add_systems(OnEnter(AppState::MenuRunning), setup_main_scene_animations) .add_systems(OnEnter(AppState::MenuRunning), setup_main_scene_animations)
.add_systems(Update, animations
.add_systems(Update, animations.run_if(in_state(AppState::AppRunning))) .run_if(in_state(AppState::AppRunning))
.after(GltfBlueprintsSet::AfterSpawn)
)
.add_systems(Update, play_animations)
/* .add_systems(Update, generate_screenshot.run_if(on_timer(Duration::from_secs_f32(0.2)))) // TODO: run once /* .add_systems(Update, generate_screenshot.run_if(on_timer(Duration::from_secs_f32(0.2)))) // TODO: run once
.add_systems( .add_systems(
Update, Update,