use crate::{GameState, heads_database::HeadsDatabase, loading_assets::GameAssets}; use bevy::{ecs::system::SystemParam, platform::collections::HashMap, prelude::*}; use std::time::Duration; #[derive(Component, Debug)] pub struct ProjectileOrigin; #[derive(Component, Debug)] pub struct AnimatedCharacter(pub usize); #[derive(SystemParam)] pub struct CharacterHierarchy<'w, 's> { descendants: Query<'w, 's, &'static Children>, projectile_origin: Query<'w, 's, &'static GlobalTransform, With>, } impl CharacterHierarchy<'_, '_> { pub fn projectile_origin(&self, entity: Entity) -> Option<&GlobalTransform> { self.descendants .iter_descendants(entity) .find_map(|child| self.projectile_origin.get(child).ok()) } } #[derive(Component, Debug, Reflect)] #[reflect(Component)] pub struct CharacterAnimations { pub idle: AnimationNodeIndex, pub run: AnimationNodeIndex, pub jump: AnimationNodeIndex, pub graph: Handle, } pub fn plugin(app: &mut App) { app.add_systems( Update, (spawn, setup_once_loaded, setup_projectile_origin).run_if(in_state(GameState::Playing)), ); #[cfg(feature = "dbg")] app.add_systems( Update, debug_show_projectile_origin.run_if(in_state(GameState::Playing)), ); } fn spawn( mut commands: Commands, mut query: Query<(Entity, &AnimatedCharacter), Added>, gltf_assets: Res>, assets: Res, heads_db: Res, ) { for (entity, character) in &mut query { let key = heads_db.head_key(character.0); let handle = assets .characters .get(format!("{key}.glb").as_str()) .unwrap_or_else(|| { //TODO: remove once we use the new format for all info!("Character not found, using default [{}]", key); &assets.characters["angry demonstrator.glb"] }); let asset = gltf_assets.get(handle).unwrap(); commands.entity(entity).insert(( Transform::from_translation(Vec3::new(0., -1.45, 0.)).with_scale(Vec3::splat(1.2)), SceneRoot(asset.scenes[0].clone()), )); } } fn setup_projectile_origin( mut commands: Commands, query: Query>, descendants: Query<&Children>, name: Query<&Name>, ) { for character in query.iter() { for child in descendants.iter_descendants(character) { let Ok(name) = name.get(child) else { continue; }; if name.as_str() == "ProjectileOrigin" { commands.entity(child).insert(ProjectileOrigin); } } } } fn setup_once_loaded( mut commands: Commands, mut query: Query<(Entity, &mut AnimationPlayer), Added>, parent: Query<&ChildOf>, animated_character: Query<&AnimatedCharacter>, assets: Res, gltf_assets: Res>, mut graphs: ResMut>, ) { for (entity, mut player) in &mut query { let Some(_animated_character) = parent .iter_ancestors(entity) .find_map(|ancestor| animated_character.get(ancestor).ok()) else { continue; }; let (_, handle) = assets.characters.iter().next().unwrap(); let asset = gltf_assets.get(handle).unwrap(); let animations = asset .named_animations .iter() .map(|(name, animation)| (name.to_string(), animation.clone())) .collect::>(); let (graph, node_indices) = AnimationGraph::from_clips([ animations["idle"].clone(), animations["run"].clone(), animations["jump"].clone(), ]); // // Insert a resource with the current scene information let graph_handle = graphs.add(graph); let animations = CharacterAnimations { idle: node_indices[0], run: node_indices[1], jump: node_indices[2], graph: graph_handle.clone(), }; let mut transitions = AnimationTransitions::new(); transitions .play(&mut player, animations.idle, Duration::ZERO) .repeat(); commands .entity(entity) .insert(AnimationGraphHandle(animations.graph.clone())) .insert(transitions) .insert(animations); } } #[cfg(feature = "dbg")] fn debug_show_projectile_origin( mut gizmos: Gizmos, query: Query<&GlobalTransform, With>, ) { for projectile_origin in query.iter() { gizmos.sphere( Isometry3d::from_translation(projectile_origin.translation()), 0.1, Color::linear_rgb(0., 1., 0.), ); } }