feat: add Captured and TractorBeam components, enhance enemy behavior with capture mechanics
This commit is contained in:
parent
1d1b927007
commit
008f9cc24a
5 changed files with 366 additions and 107 deletions
|
|
@ -1,7 +1,7 @@
|
|||
use bevy::prelude::*;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::components::{Bullet, Enemy, Invincible, Player};
|
||||
use crate::components::{Bullet, Enemy, Invincible, Player, Captured};
|
||||
use crate::constants::{
|
||||
BULLET_SIZE, PLAYER_ENEMY_COLLISION_THRESHOLD, PLAYER_INVINCIBILITY_DURATION, PLAYER_SIZE,
|
||||
PLAYER_SPEED, WINDOW_HEIGHT, WINDOW_WIDTH,
|
||||
|
|
@ -42,7 +42,7 @@ pub fn spawn_player_ship(commands: &mut Commands) {
|
|||
|
||||
pub fn move_player(
|
||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||
mut query: Query<(&mut Transform, &Player)>,
|
||||
mut query: Query<(&mut Transform, &Player), Without<Captured>>, // Don't move captured players with controls
|
||||
time: Res<Time>,
|
||||
) {
|
||||
// Using get_single_mut handles the case where player might not exist yet (or was just destroyed)
|
||||
|
|
@ -66,9 +66,94 @@ pub fn move_player(
|
|||
}
|
||||
}
|
||||
|
||||
// New system to handle captured player movement
|
||||
pub fn handle_captured_player(
|
||||
mut commands: Commands,
|
||||
time: Res<Time>,
|
||||
player_query: Query<(Entity, &Transform, &Captured)>,
|
||||
mut player_mut_query: Query<(&mut Transform, &mut Captured)>,
|
||||
enemy_query: Query<&Transform, With<Enemy>>,
|
||||
mut lives: ResMut<PlayerLives>,
|
||||
mut respawn_timer: ResMut<PlayerRespawnTimer>,
|
||||
mut next_state: ResMut<NextState<AppState>>,
|
||||
) {
|
||||
// First, collect data from all captured players
|
||||
let mut to_process = Vec::new();
|
||||
|
||||
for (entity, transform, captured) in player_query.iter() {
|
||||
// Check if the boss exists
|
||||
let boss_exists = enemy_query.get(captured.boss_entity).is_ok();
|
||||
let boss_pos = if boss_exists {
|
||||
enemy_query.get(captured.boss_entity).map(|t| t.translation).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Create a copy of the timer to check if it would finish
|
||||
let mut timer_copy = captured.timer.clone();
|
||||
timer_copy.tick(time.delta());
|
||||
|
||||
to_process.push((entity, transform.translation, boss_pos, timer_copy.finished()));
|
||||
}
|
||||
|
||||
// Now process each player separately
|
||||
for (entity, current_pos, boss_pos_opt, timer_would_finish) in to_process {
|
||||
if let Ok((mut transform, mut captured)) = player_mut_query.get_mut(entity) {
|
||||
// Tick the real timer
|
||||
captured.timer.tick(time.delta());
|
||||
|
||||
match boss_pos_opt {
|
||||
Some(boss_pos) => {
|
||||
// Boss exists, update player position
|
||||
let target_pos = boss_pos - Vec3::new(0.0, PLAYER_SIZE.y + 10.0, 0.0);
|
||||
transform.translation = current_pos.lerp(target_pos, 0.2);
|
||||
},
|
||||
None => {
|
||||
// Boss is gone, release player but lose a life
|
||||
println!("Boss is gone, releasing captured player!");
|
||||
commands.entity(entity).remove::<Captured>();
|
||||
lose_life_and_respawn(&mut commands, &mut lives, &mut respawn_timer, &mut next_state, entity);
|
||||
}
|
||||
}
|
||||
|
||||
// If capture duration expires, player escapes but loses a life
|
||||
if timer_would_finish || captured.timer.finished() {
|
||||
println!("Player escaped from capture after timer expired!");
|
||||
commands.entity(entity).remove::<Captured>();
|
||||
lose_life_and_respawn(&mut commands, &mut lives, &mut respawn_timer, &mut next_state, entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function for player life loss and respawn logic
|
||||
fn lose_life_and_respawn(
|
||||
commands: &mut Commands,
|
||||
lives: &mut ResMut<PlayerLives>,
|
||||
respawn_timer: &mut ResMut<PlayerRespawnTimer>,
|
||||
next_state: &mut ResMut<NextState<AppState>>,
|
||||
player_entity: Entity,
|
||||
) {
|
||||
// Lose a life
|
||||
lives.count = lives.count.saturating_sub(1);
|
||||
println!("Lives remaining: {}", lives.count);
|
||||
|
||||
// Destroy player
|
||||
commands.entity(player_entity).despawn();
|
||||
|
||||
if lives.count > 0 {
|
||||
respawn_timer.timer.reset();
|
||||
respawn_timer.timer.unpause();
|
||||
println!("Respawn timer started.");
|
||||
} else {
|
||||
println!("GAME OVER!");
|
||||
next_state.set(AppState::GameOver);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn player_shoot(
|
||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||
mut query: Query<(&Transform, &mut Player)>, // Should only run if player exists due to run_if
|
||||
mut query: Query<(&Transform, &mut Player), Without<Captured>>, // Only non-captured players can shoot
|
||||
mut commands: Commands,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
|
|
@ -107,7 +192,7 @@ pub fn check_player_enemy_collisions(
|
|||
mut respawn_timer: ResMut<PlayerRespawnTimer>,
|
||||
mut next_state: ResMut<NextState<AppState>>, // Resource to change state
|
||||
// Query player without Invincible component - relies on run_if condition too
|
||||
player_query: Query<(Entity, &Transform), (With<Player>, Without<Invincible>)>,
|
||||
player_query: Query<(Entity, &Transform), (With<Player>, Without<Invincible>, Without<Captured>)>, // Don't check collisions for captured players
|
||||
enemy_query: Query<(Entity, &Transform), With<Enemy>>,
|
||||
) {
|
||||
// This system only runs if player exists and is not invincible, due to run_if
|
||||
|
|
@ -133,7 +218,7 @@ pub fn check_player_enemy_collisions(
|
|||
println!("Respawn timer started.");
|
||||
} else {
|
||||
println!("GAME OVER!");
|
||||
next_state.set(AppState::GameOver); // Transition to GameOver state
|
||||
next_state.set(AppState::GameOver); // Updated for newer Bevy states API
|
||||
}
|
||||
// Important: Break after handling one collision per frame for the player
|
||||
break;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue