hud state management

Guide for hud state management

HUD State Management

Problem

The game's HUD (Health, Mana, Inventory, etc.) was visible on the Main Menu screen, which is inappropriate. The HUD should only be shown during active gameplay.

Root Cause

The ui_main_layout system in src/systems/ui/mod.rs was rendering the HUD unconditionally, without checking the current GameState.
File: src/systems/ui/mod.rs (line 226-260)
The system had a check for EditorState but not for GameState:
fn ui_main_layout(
    // ... parameters ...
    editor_state: Option<Res<crate::systems::editor::EditorState>>,
    // ... other params ...
) {
    // Hide HUD if Editor is Active
    if let Some(state) = editor_state {
        if state.is_active { return; }
    }
    // ❌ No check for GameState - HUD renders on Main Menu
    
    // ... render HUD ...
}

Solution

Add a GameState check to conditionally render the HUD only when in GameState::Playing.

Code Change

File: src/systems/ui/mod.rs
Before (lines 246-260):
fn ui_main_layout(
    // ... parameters ...
    mut coconut_ui_state: ResMut<coconut_stacking_ui::CoconutUIState>,
) {
    let ui_assets = params.ui_assets;
    // ... unpacking params ...

    // Hide HUD if Editor is Active
    if let Some(state) = editor_state {
        if state.is_active { return; }
    }
    for event in interaction_reader.read() {
        // ... event handling ...
    }
After (lines 246-264):
fn ui_main_layout(
    // ... parameters ...
    mut coconut_ui_state: ResMut<coconut_stacking_ui::CoconutUIState>,
    game_state: Res<State<crate::GameState>>, // ← Added parameter
) {
    let ui_assets = params.ui_assets;
    // ... unpacking params ...

    // Hide HUD if Editor is Active
    if let Some(state) = editor_state {
        if state.is_active { return; }
    }
    
    // Hide HUD if not in Playing state
    if *game_state.get() != crate::GameState::Playing {
        return;
    }
    
    for event in interaction_reader.read() {
        // ... event handling ...
    }

Why This Works

Bevy's State System: The State<T> resource tracks the current game state. By adding game_state: Res<State<crate::GameState>> as a system parameter, we can query the current state.
Early Return Pattern: If the game is not in Playing state (e.g., MainMenu, Tutorial, Login), the function returns early before rendering any HUD elements.
State Transitions: When the user clicks "New Game" or "Continue" in the main menu, Bevy transitions to GameState::Playing, and the HUD becomes visible.

Verification

After applying the fix:
cargo run --bin legends_client
Expected behavior:
  1. Main Menu screen shows only the menu UI (no HUD)
  2. Clicking "New Game" or "Continue" transitions to GameState::Playing
  3. HUD appears once in the game
Verification Status: ✅ CONFIRMED - HUD is now hidden on the Main Menu.

Conditional UI Rendering in Bevy

This pattern is common for state-based UI:
fn render_ui_system(
    state: Res<State<MyGameState>>,
    // ... other params ...
) {
    match state.get() {
        MyGameState::MainMenu => { /* render menu */ },
        MyGameState::Playing => { /* render HUD */ },
        MyGameState::Paused => { /* render pause menu */ },
        _ => { /* render nothing */ }
    }
}

Alternative: Run Conditions

Instead of early returns, you can use Bevy's run_if to conditionally schedule systems:
app.add_systems(
    Update, 
    ui_main_layout.run_if(in_state(GameState::Playing))
);
Pros:
  • System doesn't run at all when not needed (more efficient)
  • Cleaner separation of concerns
Cons:
  • Requires restructuring the plugin setup
  • Less flexible for multi-state UI (e.g., showing some UI in multiple states)
For the current implementation, the early return pattern was chosen because:
  1. Minimal code change (one line added)
  2. Preserves existing system structure
  3. Easy to extend with additional state checks

Files Modified

  • src/systems/ui/mod.rs (line 248): Added game_state: Res<State<crate::GameState>> parameter
  • src/systems/ui/mod.rs (lines 261-264): Added GameState::Playing check