diff --git a/assets/models/characters/angry demonstrator.glb b/assets/models/characters/angry demonstrator.glb new file mode 100644 index 0000000..22cf460 Binary files /dev/null and b/assets/models/characters/angry demonstrator.glb differ diff --git a/assets/models/characters/field medic.glb b/assets/models/characters/field medic.glb new file mode 100644 index 0000000..ff5ff70 Binary files /dev/null and b/assets/models/characters/field medic.glb differ diff --git a/assets/models/heads/goblin.glb b/assets/models/characters/goblin.glb similarity index 59% rename from assets/models/heads/goblin.glb rename to assets/models/characters/goblin.glb index f625a25..e0cc29d 100644 Binary files a/assets/models/heads/goblin.glb and b/assets/models/characters/goblin.glb differ diff --git a/assets/models/characters/mig pilot.glb b/assets/models/characters/mig pilot.glb new file mode 100644 index 0000000..559934f Binary files /dev/null and b/assets/models/characters/mig pilot.glb differ diff --git a/assets/models/heads/angry demonstrator.glb b/assets/models/heads/angry demonstrator.glb deleted file mode 100644 index 2827c66..0000000 Binary files a/assets/models/heads/angry demonstrator.glb and /dev/null differ diff --git a/assets/models/heads/field medic.glb b/assets/models/heads/field medic.glb deleted file mode 100644 index 53d69c3..0000000 Binary files a/assets/models/heads/field medic.glb and /dev/null differ diff --git a/assets/models/heads/mig pilot.glb b/assets/models/heads/mig pilot.glb deleted file mode 100644 index 8e5a126..0000000 Binary files a/assets/models/heads/mig pilot.glb and /dev/null differ diff --git a/assets/models/mig.glb b/assets/models/mig.glb deleted file mode 100644 index 9cce673..0000000 Binary files a/assets/models/mig.glb and /dev/null differ diff --git a/src/alien.rs b/src/alien.rs deleted file mode 100644 index 21b5299..0000000 --- a/src/alien.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crate::{GameState, loading_assets::GameAssets}; -use bevy::prelude::*; -use std::time::Duration; - -#[derive(Resource)] -pub struct Animations { - pub animations: Vec, - pub graph: Handle, -} - -pub fn plugin(app: &mut App) { - app.add_systems(OnEnter(GameState::Playing), setup); - app.add_systems( - Update, - (setup_once_loaded, toggle_animation).run_if(in_state(GameState::Playing)), - ); -} - -fn setup( - mut commands: Commands, - assets: Res, - mut graphs: ResMut>, -) { - // Build the animation graph - let (graph, node_indices) = AnimationGraph::from_clips([ - assets.animations_alien.get("Animation2").cloned().unwrap(), - assets.animations_alien.get("Animation1").cloned().unwrap(), - assets.animations_alien.get("Animation0").cloned().unwrap(), - ]); - - // Insert a resource with the current scene information - let graph_handle = graphs.add(graph); - commands.insert_resource(Animations { - animations: node_indices, - graph: graph_handle, - }); -} - -fn setup_once_loaded( - mut commands: Commands, - animations: Res, - mut players: Query<(Entity, &mut AnimationPlayer), Added>, -) { - for (entity, mut player) in &mut players { - let mut transitions = AnimationTransitions::new(); - - // Make sure to start the animation via the `AnimationTransitions` - // component. The `AnimationTransitions` component wants to manage all - // the animations and will get confused if the animations are started - // directly via the `AnimationPlayer`. - transitions - .play(&mut player, animations.animations[1], Duration::ZERO) - .repeat(); - - commands - .entity(entity) - .insert(AnimationGraphHandle(animations.graph.clone())) - .insert(transitions); - } -} - -fn toggle_animation( - animations: Res, - mut transitions: Query<(&mut AnimationTransitions, &mut AnimationPlayer)>, - keys: Res>, - mut animation_index: Local, -) { - if keys.just_pressed(KeyCode::KeyT) { - for (mut transition, mut player) in &mut transitions { - transition - .play( - &mut player, - animations.animations[*animation_index as usize], - Duration::from_secs(1), - ) - .repeat(); - } - - *animation_index ^= 1; // Toggle between 0 and 1 - } -} diff --git a/src/character.rs b/src/character.rs new file mode 100644 index 0000000..68c0ee2 --- /dev/null +++ b/src/character.rs @@ -0,0 +1,102 @@ +use crate::{GameState, heads_database::HeadsDatabase, loading_assets::GameAssets}; +use bevy::{prelude::*, utils::HashMap}; +use std::time::Duration; + +#[derive(Component, Debug)] +pub struct AnimatedCharacter(pub usize); + +#[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).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_once_loaded( + mut commands: Commands, + mut query: Query<(Entity, &mut AnimationPlayer), Added>, + parent_query: Query<&Parent>, + animated_character: Query<&AnimatedCharacter>, + assets: Res, + gltf_assets: Res>, + mut graphs: ResMut>, +) { + for (entity, mut player) in &mut query { + let Some(_animated_character) = parent_query + .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); + } +} diff --git a/src/loading_assets.rs b/src/loading_assets.rs index acc92ec..2469f08 100644 --- a/src/loading_assets.rs +++ b/src/loading_assets.rs @@ -38,7 +38,6 @@ pub struct AudioAssets { pub throw_explosion: Handle, #[asset(path = "sfx/abilities/jet.ogg")] pub jet: Handle, - #[asset(path = "sfx/ui/backpack_open.ogg")] pub backpack_open: Handle, #[asset(path = "sfx/ui/backpack_close.ogg")] @@ -91,21 +90,11 @@ pub struct GameAssets { #[asset(path = "models/cash.glb#Scene0")] pub mesh_cash: Handle, - #[asset(path = "models/alien_naked.glb#Scene0")] - pub mesh_alien: Handle, - - #[asset( - paths( - "models/alien_naked.glb#Animation0", - "models/alien_naked.glb#Animation1", - "models/alien_naked.glb#Animation2", - ), - collection(mapped, typed) - )] - pub animations_alien: HashMap>, - #[asset(path = "models/projectiles", collection(mapped, typed))] pub projectiles: HashMap>, + + #[asset(path = "models/characters", collection(mapped, typed))] + pub characters: HashMap>, } pub struct LoadingPlugin; diff --git a/src/main.rs b/src/main.rs index de9a80a..e324748 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,11 @@ mod abilities; mod ai; mod aim; -mod alien; mod backpack; mod camera; mod cash; mod cash_heal; +mod character; mod control; mod cutscene; mod debug; @@ -126,7 +126,7 @@ fn main() { } app.add_plugins(ai::plugin); - app.add_plugins(alien::plugin); + app.add_plugins(character::plugin); app.add_plugins(cash::plugin); app.add_plugins(player::plugin); app.add_plugins(gates::plugin); diff --git a/src/npc.rs b/src/npc.rs index d40518f..1b2db03 100644 --- a/src/npc.rs +++ b/src/npc.rs @@ -1,6 +1,7 @@ use crate::{ GameState, ai::Ai, + character::AnimatedCharacter, head::ActiveHead, head_drop::HeadDrops, heads::{ActiveHeads, HEAD_COUNT, HeadState}, @@ -44,6 +45,7 @@ fn init(mut commands: Commands, query: Query<(Entity, &EnemySpawn)>, heads_db: R ]), Ai, )) + .with_child((Name::from("body-rig"), AnimatedCharacter(id))) .observe(on_kill); } } diff --git a/src/player.rs b/src/player.rs index 9c4499c..3797461 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,15 +1,15 @@ use crate::{ GameState, - alien::Animations, camera::{CameraArmRotation, CameraTarget}, cash::{Cash, CashCollectEvent}, + character::{AnimatedCharacter, CharacterAnimations}, control::controller_common::{CharacterControllerBundle, PlayerMovement}, global_observer, head::ActiveHead, heads::{ActiveHeads, HeadChanged, HeadState}, heads_database::{HeadControls, HeadsDatabase}, hitpoints::Hitpoints, - loading_assets::{AudioAssets, GameAssets}, + loading_assets::AudioAssets, physics_layers::GameLayer, sounds::PlaySound, tb_entities::SpawnPoint, @@ -29,9 +29,6 @@ pub struct Player; #[derive(Component, Default)] struct PlayerAnimations; -#[derive(Component, Default)] -struct PlayerHeadMesh; - #[derive(Component, Default)] pub struct PlayerBodyMesh; @@ -56,7 +53,6 @@ fn spawn( mut commands: Commands, asset_server: Res, query: Query<&Transform, With>, - assets: Res, heads_db: Res, ) { let Some(spawn) = query.iter().next() else { @@ -65,9 +61,6 @@ fn spawn( let transform = Transform::from_translation(spawn.translation + Vec3::new(0., 3., 0.)); - let mesh = asset_server - .load(GltfAssetLabel::Scene(0).from_asset("models/heads/angry demonstrator.glb")); - let gravity = Vector::NEG_Y * 40.0; let collider = Collider::capsule(0.9, 1.2); @@ -94,21 +87,13 @@ fn spawn( .with_children(|parent| { parent .spawn(( - Name::from("body rig"), + Name::new("player-rig"), + Transform::default(), + Visibility::default(), PlayerBodyMesh, CameraArmRotation, - Transform::from_translation(Vec3::new(0., -1.45, 0.)) - .with_rotation(Quat::from_rotation_y(std::f32::consts::PI)) - .with_scale(Vec3::splat(1.4)), - SceneRoot(assets.mesh_alien.clone()), )) - .with_child(( - Name::from("head"), - PlayerHeadMesh, - Transform::from_translation(Vec3::new(0., 1.6, 0.)) - .with_scale(Vec3::splat(0.7)), - SceneRoot(mesh), - )); + .with_child(AnimatedCharacter(0)); }); commands.spawn(( @@ -176,29 +161,33 @@ fn setup_animations_marker_for_player( for ancestor in parent_query.iter_ancestors(entity) { if player.contains(ancestor) { commands.entity(entity).insert(PlayerAnimations); + return; } } } } fn toggle_animation( - animations: Res, mut transitions: Query< - (&mut AnimationTransitions, &mut AnimationPlayer), + ( + &mut AnimationTransitions, + &mut AnimationPlayer, + &CharacterAnimations, + ), With, >, movement: Res, ) { if movement.is_changed() { - let index = if movement.any_direction { 0 } else { 1 }; + for (mut transition, mut player, animations) in &mut transitions { + let index = if movement.any_direction { + animations.run + } else { + animations.idle + }; - for (mut transition, mut player) in &mut transitions { transition - .play( - &mut player, - animations.animations[index], - Duration::from_millis(100), - ) + .play(&mut player, index, Duration::from_millis(100)) .repeat(); } } @@ -207,13 +196,12 @@ fn toggle_animation( fn on_update_head( trigger: Trigger, mut commands: Commands, - asset_server: Res, - head: Query>, + body_mesh: Query>, mut player_head: Query<&mut ActiveHead, With>, head_db: Res, audio_assets: Res, ) { - let Ok(head) = head.get_single() else { + let Ok(body_mesh) = body_mesh.get_single() else { return; }; @@ -227,27 +215,21 @@ fn on_update_head( commands.trigger(PlaySound::Head(head_str.to_string())); - //TODO: load from dynamic asset collection? or keep lazy? - let mesh = asset_server - .load(GltfAssetLabel::Scene(0).from_asset(format!("models/heads/{}.glb", head_str))); + commands.entity(body_mesh).despawn_descendants(); - commands.entity(head).despawn_descendants(); - commands.entity(head).insert(SceneRoot(mesh)); + commands + .entity(body_mesh) + .with_child(AnimatedCharacter(trigger.0)); //TODO: make part of full character mesh later if head_db.head_stats(trigger.0).controls == HeadControls::Plane { - let mesh = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/mig.glb")); - - commands - .entity(head) - .with_child((Transform::from_xyz(0., -1., 0.), SceneRoot(mesh))) - .with_child(( - Name::new("sfx"), - AudioPlayer::new(audio_assets.jet.clone()), - PlaybackSettings { - mode: bevy::audio::PlaybackMode::Loop, - ..Default::default() - }, - )); + commands.entity(body_mesh).with_child(( + Name::new("sfx"), + AudioPlayer::new(audio_assets.jet.clone()), + PlaybackSettings { + mode: bevy::audio::PlaybackMode::Loop, + ..Default::default() + }, + )); } } diff --git a/src/tb_entities.rs b/src/tb_entities.rs index 1354748..aa6badc 100644 --- a/src/tb_entities.rs +++ b/src/tb_entities.rs @@ -117,11 +117,8 @@ pub struct EnemySpawn { impl EnemySpawn { fn on_add(mut world: DeferredWorld, entity: Entity, _id: ComponentId) { - let Some(asset_server) = world.get_resource::() else { - return; - }; - - let Some(assets) = world.get_resource::() else { + //TODO: figure out why this crashes if removed + let Some(_assets) = world.get_resource::() else { return; }; @@ -135,38 +132,17 @@ impl EnemySpawn { let mut this_transform = *this_transform; this_transform.translation += Vec3::new(0., 1., 0.); - let mesh = assets.mesh_alien.clone(); - - let head_mesh = asset_server - .load(GltfAssetLabel::Scene(0).from_asset(format!("models/heads/{}.glb", this.head))); - let head = this.head.clone(); - world - .commands() - .entity(entity) - .insert(( - this_transform, - Name::from(format!("enemy [{}]", head)), - Visibility::default(), - RigidBody::Dynamic, - Collider::capsule(0.4, 2.), - CollisionLayers::new(LayerMask(GameLayer::Npc.to_bits()), LayerMask::ALL), - LockedAxes::new().lock_rotation_z().lock_rotation_x(), - )) - .with_children(|parent| { - parent - .spawn(( - Transform::from_translation(Vec3::new(0., 1., 0.)), - SceneRoot(head_mesh), - )) - .with_child(( - Visibility::default(), - Transform::from_translation(Vec3::new(0., -2.4, 0.)) - .with_scale(Vec3::splat(1.5)), - SceneRoot(mesh), - )); - }); + world.commands().entity(entity).insert(( + this_transform, + Name::from(format!("enemy [{}]", head)), + Visibility::default(), + RigidBody::Dynamic, + Collider::capsule(0.4, 2.), + CollisionLayers::new(LayerMask(GameLayer::Npc.to_bits()), LayerMask::ALL), + LockedAxes::new().lock_rotation_z().lock_rotation_x(), + )); } }