diff --git a/TODO.md b/TODO.md
index f029d92..f21d7d7 100644
--- a/TODO.md
+++ b/TODO.md
@@ -55,7 +55,7 @@
* [ ] **Visuals**
* [ ] Replace placeholder geometric shapes with actual sprites.
* [ ] Add explosion animations/effects.
- * [ ] Implement a scrolling starfield background.
+ * [x] Implement a scrolling starfield background.
* [ ] **Audio**
* [ ] Integrate `bevy_audio`.
* [ ] Add sound effects (shooting, explosions, player death, tractor beam, etc.).
diff --git a/src/components.rs b/src/components.rs
index baf1ea2..fe5ff07 100644
--- a/src/components.rs
+++ b/src/components.rs
@@ -90,3 +90,8 @@ pub struct StartButton;
#[derive(Component)]
pub struct RestartMessage;
+
+#[derive(Component)]
+pub struct Star {
+ pub speed: f32,
+}
diff --git a/src/constants.rs b/src/constants.rs
index 0b2c1c0..c8e8a15 100644
--- a/src/constants.rs
+++ b/src/constants.rs
@@ -39,3 +39,11 @@ pub const TRACTOR_BEAM_WIDTH: f32 = 20.0;
pub const TRACTOR_BEAM_DURATION: f32 = 3.0;
pub const TRACTOR_BEAM_COLOR: Color = Color::rgba(0.5, 0.0, 0.8, 0.6);
pub const CAPTURE_DURATION: f32 = 10.0; // How long the player stays captured
+
+// Starfield constants
+pub const STAR_COUNT: usize = 150;
+pub const STAR_MIN_SIZE: f32 = 1.0;
+pub const STAR_MAX_SIZE: f32 = 3.0;
+pub const STAR_MIN_SPEED: f32 = 20.0;
+pub const STAR_MAX_SPEED: f32 = 100.0;
+pub const STAR_Z_DEPTH: f32 = -10.0; // Behind all game entities
diff --git a/src/game_state.rs b/src/game_state.rs
index 8cf8b1c..fd220ed 100644
--- a/src/game_state.rs
+++ b/src/game_state.rs
@@ -197,11 +197,10 @@ pub fn start_menu_button_system(
}
pub fn handle_restart_input(
- mut keyboard_input: ResMut>,
+ keyboard_input: Res>,
mut restart_resource: ResMut,
- mut app_state: ResMut>,
) {
- if keyboard_input.just_pressed(KeyCode::R) {
+ if keyboard_input.just_pressed(KeyCode::KeyR) {
restart_resource.pressed = true;
}
}
diff --git a/src/main.rs b/src/main.rs
index ea63050..af9585b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -10,6 +10,7 @@ pub mod enemy;
pub mod bullet;
pub mod stage;
pub mod systems;
+pub mod starfield;
use constants::{PLAYER_RESPAWN_DELAY, STARTING_LIVES, WINDOW_HEIGHT, WINDOW_WIDTH};
use resources::{ // Added StageConfigurations
@@ -36,6 +37,7 @@ use bullet::{
};
use stage::check_stage_clear;
use systems::{player_exists, player_vulnerable, setup, should_respawn_player, update_window_title};
+use starfield::scroll_starfield;
fn main() {
App::new()
@@ -115,6 +117,8 @@ fn main() {
.chain() // Ensure these run in order if needed, check_formation first
.run_if(in_state(AppState::Playing)),
)
+ // Starfield runs in all states
+ .add_systems(Update, scroll_starfield)
// UI and state management systems
.add_systems(OnEnter(AppState::StartMenu), setup_start_menu_ui)
.add_systems(OnExit(AppState::StartMenu), cleanup_start_menu_ui)
diff --git a/src/starfield.rs b/src/starfield.rs
new file mode 100644
index 0000000..711b276
--- /dev/null
+++ b/src/starfield.rs
@@ -0,0 +1,48 @@
+use bevy::prelude::*;
+
+use crate::components::Star;
+use crate::constants::{
+ STAR_COUNT, STAR_MAX_SIZE, STAR_MAX_SPEED, STAR_MIN_SIZE, STAR_MIN_SPEED,
+ STAR_Z_DEPTH, WINDOW_HEIGHT, WINDOW_WIDTH,
+};
+
+pub fn spawn_starfield(commands: &mut Commands) {
+ for _ in 0..STAR_COUNT {
+ let size = fastrand::f32() * (STAR_MAX_SIZE - STAR_MIN_SIZE) + STAR_MIN_SIZE;
+ let speed = fastrand::f32() * (STAR_MAX_SPEED - STAR_MIN_SPEED) + STAR_MIN_SPEED;
+ let brightness = fastrand::f32() * 0.5 + 0.5; // 0.5 to 1.0
+
+ commands.spawn((
+ SpriteBundle {
+ sprite: Sprite {
+ color: Color::rgb(brightness, brightness, brightness),
+ custom_size: Some(Vec2::new(size, size)),
+ ..default()
+ },
+ transform: Transform::from_translation(Vec3::new(
+ fastrand::f32() * WINDOW_WIDTH - WINDOW_WIDTH / 2.0,
+ fastrand::f32() * WINDOW_HEIGHT - WINDOW_HEIGHT / 2.0,
+ STAR_Z_DEPTH,
+ )),
+ ..default()
+ },
+ Star { speed },
+ ));
+ }
+}
+
+pub fn scroll_starfield(mut star_query: Query<(&mut Transform, &Star)>, time: Res