use crate::{ GameState, heads_database::HeadsDatabase, loading_assets::GameAssets, utils::trail::Trail, }; use bevy::{ ecs::system::SystemParam, platform::collections::HashMap, prelude::*, scene::SceneInstanceReady, }; use std::{f32::consts::PI, time::Duration}; #[derive(Component, Debug)] pub struct ProjectileOrigin; #[derive(Component, Debug)] pub struct AnimatedCharacter { head: usize, } impl AnimatedCharacter { pub fn new(head: usize) -> Self { Self { head } } } #[derive(Component, Debug)] struct AnimatedCharacterAsset(pub Handle); #[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 shooting: Option, pub graph: Handle, } pub fn plugin(app: &mut App) { app.add_systems( Update, (spawn, setup_once_loaded).run_if(in_state(GameState::Playing)), ); #[cfg(feature = "dbg")] app.add_systems( Update, debug_show_projectile_origin_and_trial.run_if(in_state(GameState::Playing)), ); } fn spawn( mut commands: Commands, query: Query<(Entity, &AnimatedCharacter), Added>, gltf_assets: Res>, assets: Res, heads_db: Res, ) { for (entity, character) in query.iter() { let key = heads_db.head_key(character.head); 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(); let mut t = Transform::from_translation(Vec3::new(0., -1.45, 0.)).with_scale(Vec3::splat(1.2)); t.rotate_y(PI); commands .entity(entity) .insert(( t, SceneRoot(asset.scenes[0].clone()), AnimatedCharacterAsset(handle.clone()), )) .observe(find_marker_bones); } } fn find_marker_bones( trigger: Trigger, mut commands: Commands, descendants: Query<&Children>, name: Query<&Name>, mut gizmo_assets: ResMut>, ) { let entity = trigger.target(); let mut origin_found = false; for child in descendants.iter_descendants(entity) { let Ok(name) = name.get(child) else { continue; }; if name.as_str() == "ProjectileOrigin" { commands.entity(child).insert(ProjectileOrigin); origin_found = true; } else if name.as_str().starts_with("Trail") { commands.entity(child).insert(( Trail::new( 20, LinearRgba::new(1., 1.0, 1., 0.5), LinearRgba::new(1., 1., 1., 0.5), ), Gizmo { handle: gizmo_assets.add(GizmoAsset::default()), line_config: GizmoLineConfig { width: 24., ..default() }, ..default() }, )); } } if !origin_found { error!("ProjectileOrigin not found"); } } fn setup_once_loaded( mut commands: Commands, mut query: Query<(Entity, &mut AnimationPlayer), Added>, parent: Query<&ChildOf>, animated_character: Query<(&AnimatedCharacter, &AnimatedCharacterAsset)>, gltf_assets: Res>, mut graphs: ResMut>, ) { for (entity, mut player) in &mut query { let Some((_character, asset)) = parent .iter_ancestors(entity) .find_map(|ancestor| animated_character.get(ancestor).ok()) else { continue; }; let asset = gltf_assets.get(asset.0.id()).unwrap(); let animations = asset .named_animations .iter() .map(|(name, animation)| (name.to_string(), animation.clone())) .collect::>(); let mut clips = vec![ animations["idle"].clone(), animations["run"].clone(), animations["jump"].clone(), ]; if let Some(shooting_animation) = animations.get("shoot") { clips.push(shooting_animation.clone()); } let (graph, node_indices) = AnimationGraph::from_clips(clips); // 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], shooting: if node_indices.len() == 4 { Some(node_indices[3]) } else { None }, 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_and_trial( mut gizmos: Gizmos, query: Query<&GlobalTransform, Or<(With, With)>>, ) { for projectile_origin in query.iter() { gizmos.sphere( Isometry3d::from_translation(projectile_origin.translation()), 0.1, Color::linear_rgb(0., 1., 0.), ); } }