From 28e4e53da9bffc1e7a855c98ab9ed66593e84b85 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Fri, 27 Jun 2025 09:31:03 +0200 Subject: [PATCH] feat: add start menu with interactive button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add StartMenu as the default game state - Create StartMenuUI and StartButton components - Implement menu UI with BGLGA title and Start Game button - Add button interaction system with hover effects - Set up proper state transitions from menu to game - Update TODO.md to mark task as completed 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- TODO.md | 2 +- src/components.rs | 7 ++++ src/game_state.rs | 99 ++++++++++++++++++++++++++++++++++++++++++++++- src/main.rs | 4 ++ 4 files changed, 110 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index af9230d..f029d92 100644 --- a/TODO.md +++ b/TODO.md @@ -64,5 +64,5 @@ * [x] Display Score, Lives, and Stage in the window title. * [ ] Display Score, Lives, and Stage on the screen using `bevy_ui`. * [ ] Implement a High Score system (saving/loading). - * [ ] Create a Start Menu state with a "Start Game" button. + * [x] Create a Start Menu state with a "Start Game" button. * [ ] Add a "Press R to Restart" message to the `GameOver` screen and implement restart logic. diff --git a/src/components.rs b/src/components.rs index 0619ee7..aed0ceb 100644 --- a/src/components.rs +++ b/src/components.rs @@ -80,3 +80,10 @@ pub struct EnemyBullet; // Game Over UI Component (might move to ui.rs later if more UI exists) #[derive(Component)] pub struct GameOverUI; + +// Start Menu UI Components +#[derive(Component)] +pub struct StartMenuUI; + +#[derive(Component)] +pub struct StartButton; diff --git a/src/game_state.rs b/src/game_state.rs index e771ffb..111a078 100644 --- a/src/game_state.rs +++ b/src/game_state.rs @@ -1,10 +1,11 @@ use bevy::prelude::*; -use crate::components::{Bullet, Enemy, GameOverUI}; // Import necessary components +use crate::components::{Bullet, Enemy, GameOverUI, StartMenuUI, StartButton}; // Import necessary components // --- Game States --- #[derive(Clone, Eq, PartialEq, Debug, Hash, Default, States)] pub enum AppState { #[default] + StartMenu, Playing, GameOver, } @@ -65,4 +66,100 @@ pub fn cleanup_game_entities( // for entity in player_query.iter() { // commands.entity(entity).despawn(); // } +} + +// --- Start Menu UI --- + +pub fn setup_start_menu_ui(mut commands: Commands) { + println!("Entering StartMenu state. Setting up UI."); + + // Root UI container + commands + .spawn(( + NodeBundle { + style: Style { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, + flex_direction: FlexDirection::Column, + ..default() + }, + ..default() + }, + StartMenuUI, + )) + .with_children(|parent| { + // Title + parent.spawn(TextBundle::from_section( + "BGLGA", + TextStyle { + font_size: 120.0, + color: Color::WHITE, + ..default() + }, + ).with_style(Style { + margin: UiRect::bottom(Val::Px(50.0)), + ..default() + })); + + // Start Game Button + parent + .spawn(( + ButtonBundle { + style: Style { + width: Val::Px(250.0), + height: Val::Px(80.0), + border: UiRect::all(Val::Px(2.0)), + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + ..default() + }, + border_color: BorderColor(Color::WHITE), + background_color: BackgroundColor(Color::rgb(0.1, 0.1, 0.5)), + ..default() + }, + StartButton, + )) + .with_children(|parent| { + parent.spawn(TextBundle::from_section( + "Start Game", + TextStyle { + font_size: 40.0, + color: Color::WHITE, + ..default() + }, + )); + }); + }); +} + +pub fn cleanup_start_menu_ui(mut commands: Commands, query: Query>) { + println!("Exiting StartMenu state. Cleaning up UI."); + for entity in query.iter() { + commands.entity(entity).despawn_recursive(); + } +} + +pub fn start_menu_button_system( + mut interaction_query: Query< + (&Interaction, &mut BackgroundColor), + (Changed, With), + >, + mut app_state: ResMut>, +) { + for (interaction, mut color) in &mut interaction_query { + match *interaction { + Interaction::Pressed => { + println!("Start button pressed! Transitioning to Playing state."); + app_state.set(AppState::Playing); + } + Interaction::Hovered => { + color.0 = Color::rgb(0.2, 0.2, 0.7); + } + Interaction::None => { + color.0 = Color::rgb(0.1, 0.1, 0.5); + } + } + } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index e074663..eeec9c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,7 @@ use resources::{ // Added StageConfigurations }; use game_state::{ cleanup_game_entities, cleanup_game_over_ui, setup_game_over_ui, AppState, + setup_start_menu_ui, cleanup_start_menu_ui, start_menu_button_system, }; use player::{ check_player_enemy_collisions, manage_invincibility, move_player, player_shoot, @@ -112,6 +113,9 @@ fn main() { .run_if(in_state(AppState::Playing)), ) // UI and state management systems + .add_systems(OnEnter(AppState::StartMenu), setup_start_menu_ui) + .add_systems(OnExit(AppState::StartMenu), cleanup_start_menu_ui) + .add_systems(Update, start_menu_button_system.run_if(in_state(AppState::StartMenu))) .add_systems(OnEnter(AppState::GameOver), setup_game_over_ui) .add_systems(OnExit(AppState::Playing), cleanup_game_entities) .add_systems(OnExit(AppState::GameOver), cleanup_game_over_ui)