Files
HEDZReloaded/src/character.rs
2025-04-29 00:14:25 +02:00

157 lines
4.8 KiB
Rust

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<ProjectileOrigin>>,
}
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<AnimationGraph>,
}
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<AnimatedCharacter>>,
gltf_assets: Res<Assets<Gltf>>,
assets: Res<GameAssets>,
heads_db: Res<HeadsDatabase>,
) {
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<Entity, Added<AnimationPlayer>>,
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<AnimationPlayer>>,
parent: Query<&ChildOf>,
animated_character: Query<&AnimatedCharacter>,
assets: Res<GameAssets>,
gltf_assets: Res<Assets<Gltf>>,
mut graphs: ResMut<Assets<AnimationGraph>>,
) {
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::<HashMap<_, _>>();
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<ProjectileOrigin>>,
) {
for projectile_origin in query.iter() {
gizmos.sphere(
Isometry3d::from_translation(projectile_origin.translation()),
0.1,
Color::linear_rgb(0., 1., 0.),
);
}
}