From 193da4ad3a417ea1c66c78b278763034ef5bb375 Mon Sep 17 00:00:00 2001 From: Fabien JUIF Date: Wed, 1 Nov 2023 12:17:25 +0100 Subject: [PATCH] :sparkles: minions damage players --- src/common.rs | 3 ++ src/health_bar.rs | 62 +++++++++++++++++++++++++++++++--------- src/main.rs | 6 +++- src/minions.rs | 73 +++++++++++++++++++++++++++++++++++++++++++++-- src/racks.rs | 9 ++---- 5 files changed, 131 insertions(+), 22 deletions(-) diff --git a/src/common.rs b/src/common.rs index a616fbd..663d015 100644 --- a/src/common.rs +++ b/src/common.rs @@ -18,3 +18,6 @@ pub struct Team { pub id: String, pub color: Color, } + +#[derive(Component)] +pub struct Player; diff --git a/src/health_bar.rs b/src/health_bar.rs index fd5b4af..bdf5a50 100644 --- a/src/health_bar.rs +++ b/src/health_bar.rs @@ -2,37 +2,73 @@ use bevy::prelude::*; #[derive(Component)] pub struct Health { - pub health: f32, + pub value: f32, + pub max: f32, +} + +impl Health { + pub fn new(max: f32) -> Self { + Self { value: max, max } + } } #[derive(Component)] pub struct HealthBar { pub entity: Entity, pub translation: Vec3, + pub size: Vec2, } pub struct HealthBarPlugin; impl Plugin for HealthBarPlugin { fn build(&self, app: &mut App) { - app.add_systems(PostUpdate, update_health_bar_position); + app.add_systems( + PostUpdate, + ( + update_health_bar_position, + update_health_bar_visual, + clear_orphans_healthbars, + ), + ); } } -fn update_health_bar_position( +fn clear_orphans_healthbars( mut commands: Commands, + query_health: Query<&Health>, + mut query: Query<(&HealthBar, Entity)>, +) { + for (health_bar, entity) in &mut query { + if !query_health.contains(health_bar.entity) { + debug!("health bar alone, unspawning it"); + commands.entity(entity).despawn_recursive(); + } + } +} + +fn update_health_bar_position( query_health: Query<&GlobalTransform, With>, - mut query: Query<(&mut Transform, &HealthBar, Entity)>, + mut query: Query<(&mut Transform, &HealthBar)>, ) { - for (mut transform, health_bar, entity) in &mut query { - match query_health.get(health_bar.entity) { - Ok(parent_transform) => { - transform.translation = - parent_transform.to_scale_rotation_translation().2 + health_bar.translation; - } - Err(_) => { - debug!("health bar alone, unspawning it"); - commands.entity(entity).despawn_recursive(); + for (mut transform, health_bar) in &mut query { + if let Ok(parent_transform) = query_health.get(health_bar.entity) { + transform.translation = + parent_transform.to_scale_rotation_translation().2 + health_bar.translation; + } + } +} + +fn update_health_bar_visual( + query_health: Query<&Health>, + mut query: Query<(&mut Sprite, &HealthBar)>, +) { + for (mut sprite, health_bar) in &mut query { + if let Ok(health) = query_health.get(health_bar.entity) { + if health.value >= 0. { + let mut size = health_bar.size; + size.x = health.value * health_bar.size.x / health.max; + sprite.custom_size = Some(size); } } } diff --git a/src/main.rs b/src/main.rs index 66ed2ff..96cd8be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -84,8 +84,10 @@ fn setup(mut commands: Commands) { // RigidBody::KinematicVelocityBased, RigidBody::Dynamic, Collider::cuboid(25.0, 25.), + ActiveEvents::COLLISION_EVENTS, LocalPlayer {}, - Health { health: 100. }, + Player {}, + Health::new(100.), Name("local_player".to_string()), Team { id: "a".to_string(), @@ -109,6 +111,7 @@ fn setup(mut commands: Commands) { .id(); commands.spawn(( + // TODO: do a health bundle and move it to heath_bar crate SpriteBundle { sprite: Sprite { color: DEFAULT_HEALTH_COLOR, @@ -120,6 +123,7 @@ fn setup(mut commands: Commands) { HealthBar { entity, translation: Vec3::new(0.0, 40.0, 0.1), + size: Vec2::new(50.0, 5.0), // TODO: once a bundle make sure this initialise the sprite }, )); } diff --git a/src/minions.rs b/src/minions.rs index c1faab7..412fe98 100644 --- a/src/minions.rs +++ b/src/minions.rs @@ -23,7 +23,7 @@ struct Minion { impl Plugin for MinionsPlugin { fn build(&self, app: &mut App) { - app.add_systems(Update, (update_move_minions, destroy_minions)); + app.add_systems(Update, (update_move_minions, destroy_minions, hurt_player)); } } @@ -48,6 +48,7 @@ pub fn spawn_minion(commands: &mut Commands, transform: &Transform, team: Team) }, RigidBody::Dynamic, Collider::cuboid(5.0, 5.), + ActiveEvents::COLLISION_EVENTS, // Restitution::coefficient(2.), // Friction::coefficient(2.), Minion { @@ -58,12 +59,13 @@ pub fn spawn_minion(commands: &mut Commands, transform: &Transform, team: Team) bevy::time::TimerMode::Once, ), }, - Health { health: 20. }, + Health::new(20.), team, )) .id(); commands.spawn(( + // TODO: do a health bundle and move it to heath_bar crate SpriteBundle { sprite: Sprite { color: DEFAULT_HEALTH_COLOR, @@ -75,6 +77,7 @@ pub fn spawn_minion(commands: &mut Commands, transform: &Transform, team: Team) HealthBar { entity, translation: Vec3::new(0.0, 15.0, 0.1), + size: Vec2::new(10.0, 5.0), // TODO: once a bundle make sure this initialise the sprite }, )); @@ -144,3 +147,69 @@ fn destroy_minions( } } } + +// maybe this is a bad idea to have a system per component since the collision event is having all contacts +// it makes us loop inside collision events multiple time +fn hurt_player( + mut query_players: Query<(Entity, &Team, &mut Health), With>, + query_minions: Query<(Entity, &Team), With>, + mut collision_events: EventReader, + // mut contact_force_events: EventReader, +) { + for collision_event in collision_events.iter() { + match collision_event { + CollisionEvent::Started(e1, e2, _) => { + let minion = match query_minions + .get(*e1) + .ok() + .or_else(|| query_minions.get(*e2).ok()) + { + None => continue, + Some(p) => p, + }; + + // to avoid to borrow twice the query check with which entity the minion is resolved + // and take the other one to check if this is a player, this hacky way of doing it + // I am sure rust can resolve this better, here the first attempt in case someone + // want to explain me how to resolve it with Rust + // + // let player = match query_players + // .get_mut(*e1) + // .ok() + // .or_else(|| query_players.get_mut(*e2).ok()) + // { + // None => continue, + // Some(p) => p, + // }; + // + let player = if query_players.contains(*e1) { + query_players.get_mut(*e1).ok() + } else { + query_players.get_mut(*e2).ok() + }; + let mut player = match player { + None => continue, + Some(p) => p, + }; + + // if they are from the same team, do nothing special + if player.1.id == minion.1.id { + continue; + } + + // hurt the player + player.2.value -= 1.; + if player.2.value < 0. { + player.2.value = 0. + } + + trace!( + "minion {} collision with the player {}", + minion.1.id, + player.1.id + ) + } + CollisionEvent::Stopped(_, _, _) => {} + } + } +} diff --git a/src/racks.rs b/src/racks.rs index 62fc61b..ec2bb0b 100644 --- a/src/racks.rs +++ b/src/racks.rs @@ -1,8 +1,5 @@ use bevy::{ - prelude::{ - default, info, trace, App, Color, Commands, Component, Plugin, Query, Res, Startup, - Transform, Update, Vec2, - }, + prelude::*, sprite::{Sprite, SpriteBundle}, time::{Time, Timer, TimerMode}, }; @@ -50,7 +47,7 @@ fn spawn_minions( // we are ready to start spawning if rack.minion_spawn_timer.just_finished() { - info!("rack ready to spawn minions!"); + debug!("[rack] ready to spawn minions!"); rack.minion_spawned_count = 0; rack.minion_spawning = true; } @@ -65,7 +62,7 @@ fn spawn_minions( rack.minion_spawned_count += 1; if rack.minion_spawned_count >= rack.minion_spawn_count { - info!("every minions are spawned!"); + debug!("[rack] every minions are spawned!"); rack.minion_spawning = false; } }