Skip to content

Commit

Permalink
✨ minion explosion visual
Browse files Browse the repository at this point in the history
  • Loading branch information
fabienjuif committed Nov 11, 2023
1 parent 765237f commit 4ce1e60
Showing 1 changed file with 120 additions and 43 deletions.
163 changes: 120 additions & 43 deletions src/minions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@ const MINION_SCALE: f32 = 190.;
const DESTROY_MINIONS_AFTER_SECS: f32 = 120.;
const DECAY_VALUE_PER_SEC: f32 = 10.;
const REWARDS_GOLD: f32 = 1.;
const MINION_TOUCH_DAMAGE: f32 = 80.;

pub struct MinionsPlugin;

// TODO: move this into common
#[derive(Component)]
struct TimeDestroyable {
timer: Timer,
}

#[derive(Component)]
struct Minion {
destroy_timer: Timer,
had_exploded: bool,
}

impl Plugin for MinionsPlugin {
Expand All @@ -26,7 +31,7 @@ impl Plugin for MinionsPlugin {
Update,
(update_move_minions, check_collisions_minions, decay_life),
)
.add_systems(PostUpdate, destroy_minions);
.add_systems(PostUpdate, (destroy_minions, destroy_after_timer));
}
}

Expand All @@ -43,6 +48,7 @@ pub struct MinionBundle {
body: RigidBody,
collider: Collider,
events: ActiveEvents,
timer_destroyable: TimeDestroyable,
}

impl MinionBundle {
Expand Down Expand Up @@ -70,12 +76,7 @@ impl MinionBundle {
// ..default()
// },
minion: Minion {
// to avoid leaks
// maybe a better option on top of that is to leach health every seconds on minions and make them die!
destroy_timer: Timer::from_seconds(
DESTROY_MINIONS_AFTER_SECS,
bevy::time::TimerMode::Once,
),
had_exploded: false,
},
health: Health::new(20.)
.with_health_bar_position(Vec3::new(0.0, 15.0, 0.1))
Expand All @@ -86,6 +87,48 @@ impl MinionBundle {
body: RigidBody::Dynamic,
collider: Collider::ball(6.),
events: ActiveEvents::COLLISION_EVENTS,
timer_destroyable: TimeDestroyable {
timer: Timer::from_seconds(DESTROY_MINIONS_AFTER_SECS, bevy::time::TimerMode::Once),
},
}
}
}

#[derive(Component)]
struct Explosion;

#[derive(Bundle)]
struct ExplosionBundle {
mesh: MaterialMesh2dBundle<ColorMaterial>,
explosion: Explosion,
team: Team,
sensor: Sensor,
collider: Collider,
timer_destroyable: TimeDestroyable,
}

impl ExplosionBundle {
pub fn new(
meshes: &mut ResMut<Assets<Mesh>>,
materials: &mut ResMut<Assets<ColorMaterial>>,
translation: Vec3,
team: Team,
) -> Self {
let radius = 20.;
ExplosionBundle {
mesh: MaterialMesh2dBundle {
mesh: meshes.add(shape::Circle::new(radius).into()).into(),
material: materials.add(ColorMaterial::from(team.color)),
transform: Transform::from_translation(translation),
..default()
},
explosion: Explosion,
team,
collider: Collider::ball(radius),
sensor: Sensor,
timer_destroyable: TimeDestroyable {
timer: Timer::from_seconds(0.5, bevy::time::TimerMode::Once),
},
}
}
}
Expand Down Expand Up @@ -147,87 +190,121 @@ fn update_move_minions(
}

fn destroy_minions(
time: Res<Time>,
mut commands: Commands,
mut query: Query<(&mut Minion, &Transform, &Health, Entity)>,
query: Query<(&Transform, &Health, Entity), With<Minion>>,
) {
let mut kill = |entity| {
trace!("Unspawning Minion: {:?}", entity);
commands.entity(entity).despawn_recursive();
};

for (mut minion, transform, health, entity) in &mut query {
for (transform, health, entity) in query.iter() {
// edge of the world
if transform.translation.x.abs() >= GAME_MAX_WIDTH / 2.
|| transform.translation.y.abs() >= GAME_MAX_HEIGHT / 2.
{
kill(entity);
}

// too old
if minion.destroy_timer.tick(time.delta()).just_finished() {
kill(entity);
}

// just not enough health
if health.is_dead() {
kill(entity);
}
}
}

// TODO: move this into common plugin
fn destroy_after_timer(
time: Res<Time>,
mut commands: Commands,
mut query: Query<(&mut TimeDestroyable, Entity)>,
) {
for (mut time_destroyable, entity) in &mut query {
if time_destroyable.timer.tick(time.delta()).just_finished() {
commands.entity(entity).despawn_recursive();
}
}
}

// 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
// TODO: We can have a more global system to handle that in one loop querying "atker" component (with a team),
// TODO: and putting reward in the team rather than in the player, or we want to stick of having reward on each entity so when a entity die its reward are huge?
// FIXME: With explosion implem, it can be simplify
fn check_collisions_minions(
mut query_minions: Query<(&Team, &mut Health), With<Minion>>,
mut query_hit_entities: Query<(&Team, &mut Health), Without<Minion>>,
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
mut collision_events: EventReader<CollisionEvent>,
// queries
mut query_minions: Query<(&Transform, &Team, &mut Minion), With<Minion>>,
query_hit_entities: Query<&Team, Without<Minion>>,
) {
for collision_event in collision_events.iter() {
match collision_event {
CollisionEvent::Started(e1, e2, _) => {
// between minions
if query_minions.contains(*e1) && query_minions.contains(*e2) {
let [(team_a, mut health_a), (team_b, mut health_b)] =
let [(transform_a, team_a, mut minion_a), (_, team_b, _)] =
match query_minions.get_many_mut([*e1, *e2]) {
Err(_) => continue,
Ok(m) => m,
};

// if minion a already exploded
if minion_a.had_exploded {
continue;
}

// if they are from the same team, do nothing special
if team_a.id == team_b.id {
continue;
}

// hurt each other (TODO: maybe they are hurting each other twice if the couple is twice in the events (a, b) and (b, a))
health_a.hit(MINION_TOUCH_DAMAGE);
health_b.hit(MINION_TOUCH_DAMAGE);
commands.spawn(ExplosionBundle::new(
&mut meshes,
&mut materials,
transform_a.translation,
team_a.clone(),
));

minion_a.had_exploded = true;

continue;
}

if let Some((minion_team, _)) = query_minions
// minion vs others
let (minion_transform, minion_team, mut minion) = match query_minions.get_mut(*e1) {
Err(_) => match query_minions.get_mut(*e2) {
Err(_) => continue,
Ok(value) => value,
},
Ok(value) => value,
};

let Ok(team) = query_hit_entities
.get(*e1)
.ok()
.or_else(|| query_minions.get(*e2).ok())
{
if let Ok((team, mut health)) = query_hit_entities.get_mut(*e1) {
// if they are from the same team, do nothing special
if minion_team.id == team.id {
continue;
}

health.hit(MINION_TOUCH_DAMAGE);
} else if let Ok((team, mut health)) = query_hit_entities.get_mut(*e2) {
// if they are from the same team, do nothing special
if minion_team.id == team.id {
continue;
}

health.hit(MINION_TOUCH_DAMAGE);
}
.or_else(|_| query_hit_entities.get(*e2))
else {
continue;
};

if minion.had_exploded {
continue;
}

// if they are from the same team, do nothing special
if minion_team.id == team.id {
continue;
}

commands.spawn(ExplosionBundle::new(
&mut meshes,
&mut materials,
minion_transform.translation,
minion_team.clone(),
));

minion.had_exploded = true;
}
CollisionEvent::Stopped(_, _, _) => {}
}
Expand Down

0 comments on commit 4ce1e60

Please sign in to comment.