diff --git a/README.md b/README.md index 7989a47..654bada 100644 --- a/README.md +++ b/README.md @@ -59,9 +59,9 @@ nix develop --command bash -c "cargo build" **2. Enemy Behavior - Formations & Attack Patterns:** * **Enemy Formations:** This is a core Galaga feature. - * Define target positions for the enemy formation on screen. - * Give enemies an `Entering` state/component: They fly onto the screen following predefined paths (curves, waypoints). - * Give enemies a `Formation` state/component: Once they reach their target position, they stop and hold formation. + * ~~Define target positions for the enemy formation on screen.~~ **(DONE)** + * ~~Give enemies an `Entering` state/component: They fly onto the screen following predefined paths (curves, waypoints).~~ **(DONE - Basic linear path implemented)** + * Give enemies a `Formation` state/component: Once they reach their target position, they stop and hold formation. **(DONE - Stop implemented by removing FormationTarget)** * **Enemy Attack Dives:** * Give enemies an `Attacking` state/component. * Periodically trigger enemies in the formation to switch to the `Attacking` state. diff --git a/src/main.rs b/src/main.rs index 5970697..94567d1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,7 +52,14 @@ struct Invincible { #[derive(Component)] struct FormationTarget { - position: Vec3, + position: Vec3, +} + +#[derive(Component, Clone, PartialEq)] +enum EnemyState { + Entering, // Flying onto the screen towards formation target + InFormation, // Holding position in the formation + Attacking, // Diving towards the player (to be implemented) } // --- Resources --- #[derive(Resource)] @@ -83,7 +90,12 @@ struct CurrentStage { #[derive(Resource)] struct FormationState { next_slot_index: usize, - total_spawned_this_stage: usize, + total_spawned_this_stage: usize, +} + +#[derive(Resource)] +struct AttackDiveTimer { + timer: Timer, } // --- Game Over UI --- @@ -198,8 +210,9 @@ fn main() { }) .insert_resource(Score { value: 0 }) // Initialize score .insert_resource(CurrentStage { number: 1, waiting_for_clear: false }) // Initialize stage and flag - .insert_resource(FormationState { next_slot_index: 0, total_spawned_this_stage: 0 }) // Initialize formation state - // Add Systems + .insert_resource(FormationState { next_slot_index: 0, total_spawned_this_stage: 0 }) // Initialize formation state + .insert_resource(AttackDiveTimer { timer: Timer::new(Duration::from_secs_f32(3.0), TimerMode::Repeating)}) // Initialize attack timer (e.g., every 3 seconds) + // Add Systems .add_systems(Startup, setup) // Systems running only when Playing .add_systems(Update, ( @@ -211,8 +224,9 @@ fn main() { check_bullet_collisions, check_player_enemy_collisions.run_if(player_vulnerable), respawn_player.run_if(should_respawn_player), - manage_invincibility, - // check_stage_clear is now added separately below using .pipe(system) + manage_invincibility, + trigger_attack_dives, // Add system to trigger dives + // check_stage_clear is now added separately below using .pipe(system) // Game Over check is now implicit in check_player_enemy_collisions ).run_if(in_state(AppState::Playing))) .add_systems(Update, check_stage_clear.run_if(in_state(AppState::Playing))) // Add world-based system directly @@ -353,11 +367,12 @@ fn spawn_enemies( mut stage: ResMut, mut formation_state: ResMut, // Add formation state resource ) { - timer.timer.tick(time.delta()); + // Tick the timer every frame + timer.timer.tick(time.delta()); // Only spawn if we haven't spawned the full formation for this stage yet - // AND the timer is finished - if formation_state.total_spawned_this_stage < FORMATION_ENEMY_COUNT && timer.timer.tick(time.delta()).just_finished() { + // AND the timer just finished this frame + if formation_state.total_spawned_this_stage < FORMATION_ENEMY_COUNT && timer.timer.just_finished() { // Calculate grid position let slot_index = formation_state.next_slot_index; let row = slot_index / FORMATION_COLS; @@ -382,9 +397,10 @@ fn spawn_enemies( transform: Transform::from_translation(Vec3::new(spawn_x, spawn_y, 0.0)), ..default() }, - Enemy, - FormationTarget { position: target_pos }, // Add the target position - )); + Enemy, + FormationTarget { position: target_pos }, + EnemyState::Entering, // Start in Entering state + )); formation_state.next_slot_index = (slot_index + 1) % FORMATION_ENEMY_COUNT; // Cycle through slots if needed, though we stop spawning formation_state.total_spawned_this_stage += 1; @@ -403,39 +419,60 @@ fn spawn_enemies( fn move_enemies( // Query now includes FormationTarget - mut query: Query<(Entity, &mut Transform, &FormationTarget), With>, - time: Res