iii
Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
This commit is contained in:
parent
ef055fe3c5
commit
3dbfb9dac1
11 changed files with 1085 additions and 867 deletions
189
src/player.rs
Normal file
189
src/player.rs
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
use bevy::prelude::*;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::components::{Bullet, Enemy, Invincible, Player};
|
||||
use crate::constants::{
|
||||
BULLET_SIZE, PLAYER_ENEMY_COLLISION_THRESHOLD, PLAYER_INVINCIBILITY_DURATION, PLAYER_SIZE,
|
||||
PLAYER_SPEED, WINDOW_HEIGHT, WINDOW_WIDTH,
|
||||
};
|
||||
use crate::game_state::AppState;
|
||||
use crate::resources::{PlayerLives, PlayerRespawnTimer};
|
||||
|
||||
// Helper to spawn player (used in setup and respawn)
|
||||
pub fn spawn_player_ship(commands: &mut Commands) {
|
||||
commands.spawn((
|
||||
SpriteBundle {
|
||||
sprite: Sprite {
|
||||
color: Color::rgb(0.0, 0.5, 1.0),
|
||||
custom_size: Some(PLAYER_SIZE),
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_translation(Vec3::new(
|
||||
0.0,
|
||||
-WINDOW_HEIGHT / 2.0 + PLAYER_SIZE.y / 2.0 + 20.0,
|
||||
0.0,
|
||||
)),
|
||||
..default()
|
||||
},
|
||||
Player {
|
||||
speed: PLAYER_SPEED,
|
||||
shoot_cooldown: Timer::new(Duration::from_secs_f32(0.3), TimerMode::Once),
|
||||
},
|
||||
// Player starts invincible for a short time
|
||||
Invincible {
|
||||
timer: Timer::new(
|
||||
Duration::from_secs_f32(PLAYER_INVINCIBILITY_DURATION),
|
||||
TimerMode::Once,
|
||||
),
|
||||
},
|
||||
));
|
||||
println!("Player spawned!");
|
||||
}
|
||||
|
||||
pub fn move_player(
|
||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||
mut query: Query<(&mut Transform, &Player)>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
// Using get_single_mut handles the case where player might not exist yet (or was just destroyed)
|
||||
if let Ok((mut transform, player)) = query.get_single_mut() {
|
||||
let mut direction = 0.0;
|
||||
|
||||
if keyboard_input.pressed(KeyCode::KeyA) || keyboard_input.pressed(KeyCode::ArrowLeft) {
|
||||
direction -= 1.0;
|
||||
}
|
||||
|
||||
if keyboard_input.pressed(KeyCode::KeyD) || keyboard_input.pressed(KeyCode::ArrowRight) {
|
||||
direction += 1.0;
|
||||
}
|
||||
|
||||
transform.translation.x += direction * player.speed * time.delta_seconds();
|
||||
let half_player_width = PLAYER_SIZE.x / 2.0;
|
||||
transform.translation.x = transform.translation.x.clamp(
|
||||
-WINDOW_WIDTH / 2.0 + half_player_width,
|
||||
WINDOW_WIDTH / 2.0 - half_player_width,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 commands: Commands,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
if let Ok((player_transform, mut player)) = query.get_single_mut() {
|
||||
player.shoot_cooldown.tick(time.delta());
|
||||
|
||||
if (keyboard_input.just_pressed(KeyCode::Space)
|
||||
|| keyboard_input.just_pressed(KeyCode::ArrowUp))
|
||||
&& player.shoot_cooldown.finished()
|
||||
{
|
||||
player.shoot_cooldown.reset();
|
||||
|
||||
let bullet_start_pos = player_transform.translation
|
||||
+ Vec3::Y * (PLAYER_SIZE.y / 2.0 + BULLET_SIZE.y / 2.0);
|
||||
|
||||
commands.spawn((
|
||||
SpriteBundle {
|
||||
sprite: Sprite {
|
||||
color: Color::rgb(1.0, 1.0, 1.0),
|
||||
custom_size: Some(BULLET_SIZE),
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_translation(bullet_start_pos),
|
||||
..default()
|
||||
},
|
||||
Bullet,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Modified Collision Check for Player vs Enemy
|
||||
pub fn check_player_enemy_collisions(
|
||||
mut commands: Commands,
|
||||
mut lives: ResMut<PlayerLives>,
|
||||
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>)>,
|
||||
enemy_query: Query<(Entity, &Transform), With<Enemy>>,
|
||||
) {
|
||||
// This system only runs if player exists and is not invincible, due to run_if
|
||||
if let Ok((player_entity, player_transform)) = player_query.get_single() {
|
||||
for (enemy_entity, enemy_transform) in enemy_query.iter() {
|
||||
let distance = player_transform
|
||||
.translation
|
||||
.distance(enemy_transform.translation);
|
||||
|
||||
if distance < PLAYER_ENEMY_COLLISION_THRESHOLD {
|
||||
println!("Player hit by enemy!");
|
||||
commands.entity(enemy_entity).despawn(); // Despawn enemy
|
||||
|
||||
lives.count = lives.count.saturating_sub(1); // Decrement lives safely
|
||||
println!("Lives remaining: {}", lives.count);
|
||||
|
||||
commands.entity(player_entity).despawn(); // Despawn player
|
||||
|
||||
if lives.count > 0 {
|
||||
// Start the respawn timer
|
||||
respawn_timer.timer.reset();
|
||||
respawn_timer.timer.unpause();
|
||||
println!("Respawn timer started.");
|
||||
} else {
|
||||
println!("GAME OVER!");
|
||||
next_state.set(AppState::GameOver); // Transition to GameOver state
|
||||
}
|
||||
// Important: Break after handling one collision per frame for the player
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// New System: Respawn Player
|
||||
pub fn respawn_player(
|
||||
mut commands: Commands,
|
||||
time: Res<Time>,
|
||||
mut respawn_timer: ResMut<PlayerRespawnTimer>,
|
||||
// No player query needed here due to run_if condition
|
||||
) {
|
||||
// Tick the timer only if it's actually running
|
||||
if respawn_timer.timer.tick(time.delta()).just_finished() {
|
||||
println!("Respawn timer finished. Spawning player.");
|
||||
spawn_player_ship(&mut commands);
|
||||
respawn_timer.timer.pause(); // Pause timer until next death
|
||||
}
|
||||
}
|
||||
|
||||
// New System: Manage Invincibility
|
||||
pub fn manage_invincibility(
|
||||
mut commands: Commands,
|
||||
time: Res<Time>,
|
||||
mut query: Query<(Entity, &mut Invincible, Option<&mut Visibility>), With<Player>>,
|
||||
) {
|
||||
for (entity, mut invincible, mut visibility) in query.iter_mut() {
|
||||
invincible.timer.tick(time.delta());
|
||||
|
||||
// Blinking effect (optional)
|
||||
if let Some(ref mut vis) = visibility {
|
||||
// Blink roughly 5 times per second
|
||||
let elapsed_secs = invincible.timer.elapsed_secs();
|
||||
**vis = if (elapsed_secs * 10.0).floor() % 2.0 == 0.0 {
|
||||
Visibility::Visible
|
||||
} else {
|
||||
Visibility::Hidden
|
||||
};
|
||||
}
|
||||
|
||||
if invincible.timer.finished() {
|
||||
println!("Invincibility finished.");
|
||||
commands.entity(entity).remove::<Invincible>();
|
||||
// Ensure player is visible when invincibility ends
|
||||
if let Some(ref mut vis) = visibility {
|
||||
**vis = Visibility::Visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue