ui components

Guide for ui components

UI Implementation

Spell Book UI

File: src/systems/ui/spell_book_ui.rs

Architecture

The Spell Book is integrated into the main game UI as a tab (not a standalone window), matching the pattern of Inventory and Stats tabs.

State Management

#[derive(Resource, Default)]
pub struct SpellBookState {
    pub selected_tab: SpellBookTab,
}

#[derive(Default, PartialEq)]
pub enum SpellBookTab {
    #[default]
    Combat,
    Utility,
    Teleport,
}

Main Render Function

pub fn render_magic_tab(
    ui: &mut egui::Ui,
    spell_book_state: &mut SpellBookState,
    skills_data: &SkillsData,
    player_entity: Entity,
    skills: &Skills,
    active_spell: Option<&ActiveSpell>,
    select_spell_events: &mut EventWriter<SelectSpellEvent>,
    coconut_ui_state: &mut CoconutUIState,
)
Called from: ui_main_layout in src/systems/ui/mod.rs when Magic tab is active

Tab Rendering

Combat Tab

fn render_combat_spells(
    ui: &mut egui::Ui,
    skills_data: &SkillsData,
    magic_level: i32,
    active_spell: Option<&ActiveSpell>,
    player_entity: Entity,
    select_spell_events: &mut EventWriter<SelectSpellEvent>,
)
Features:
  • Sorts spells by level requirement
  • Shows spell name, level, max hit
  • Displays bija costs (e.g., "2x Air, 1x Chandra")
  • Highlights selected spell with green background
  • Grays out spells above player's level
  • Tooltips on hover showing full details
  • Click to select/deselect spell (dispatches SelectSpellEvent)
Layout: Grid of spell buttons

Utility Tab

fn render_utility_spells(
    ui: &mut egui::Ui,
    skills_data: &SkillsData,
    magic_level: i32,
)
Features:
  • Lists utility spells (healing, buffs, etc.)
  • Shows effect description
  • Displays bija costs
  • Level-based availability

Teleport Tab

fn render_teleport_spells(
    ui: &mut egui::Ui,
    skills_data: &SkillsData,
    magic_level: i32,
)
Features:
  • Lists teleport recipes
  • Shows destination
  • Displays required bijas for each eye
  • Level requirements
  • XP rewards

Integration Pattern

In src/systems/ui/mod.rs:
// Module declaration
mod spell_book_ui;

// Resource initialization
app.init_resource::<spell_book_ui::SpellBookState>()

// In ui_main_layout match statement
UiTab::Magic => {
    if let Ok((player_entity, skills, active_spell)) = player_q.get_single() {
        spell_book_ui::render_magic_tab(
            ui,
            &mut spell_book_state,
            &skills_data,
            player_entity,
            skills,
            active_spell,
            &mut select_spell_writer,
            &mut coconut_ui_state
        );
    }
}

Coconut Stacking UI

File: src/systems/ui/coconut_stacking_ui.rs

Architecture

Standalone egui Window that can be toggled on/off.

State Management

#[derive(Resource, Default)]
pub struct CoconutUIState {
    pub is_open: bool,
    pub eye_1: Option<String>, // Bija ID
    pub eye_2: Option<String>,
    pub eye_3: Option<String>,
}

Main Render Function

pub fn render_coconut_ui(
    mut contexts: EguiContexts,
    mut ui_state: ResMut<CoconutUIState>,
    player_query: Query<(Entity, &Inventory), With<Player>>,
    skills_data: Res<SkillsData>,
    mut prepare_event: EventWriter<PrepareCoconutEvent>,
)
Called from: Update schedule in src/systems/ui/mod.rs

UI Layout

┌─────────────────────────────┐
│   Coconut Ritual            │
├─────────────────────────────┤
│ Eye 1 (Top)    Eye 2 (Left) │
│ [Vishnu]       [Air]        │
│                             │
│      Eye 3 (Right)          │
│      [Earth]                │
├─────────────────────────────┤
│ Available Bijas:            │
│ [Air] [Water] [Earth]...    │
├─────────────────────────────┤
│    [Charge Coconut]         │
│    [Close]                  │
└─────────────────────────────┘

Features

  1. Eye Slots: 3 vertical slots showing placed bijas
    • Click placed bija to remove it
    • Shows "Empty" if no bija placed
  2. Available Bijas: Horizontal scrollable list
    • Iterates through known bija IDs
    • Only shows bijas player has in inventory
    • Click to place in first empty eye
  3. Charge Button:
    • Enabled only when all 3 eyes filled
    • Validates against known recipes
    • Dispatches PrepareCoconutEvent if valid
    • Clears UI and closes window on success
  4. Recipe Matching Logic:
let mut found_recipe = None;
for recipe in &skills_data.teleport_recipes {
    let mut match_1 = false;
    let mut match_2 = false;
    let mut match_3 = false;
    
    for bija_req in &recipe.coconut_bijas {
        if bija_req.eye == 1 && Some(&bija_req.bija_id) == ui_state.eye_1.as_ref() { 
            match_1 = true; 
        }
        // ... similar for eye 2 and 3
    }
    
    if match_1 && match_2 && match_3 {
        found_recipe = Some(recipe.teleport_id.clone());
        break;
    }
}

Toggle Mechanism

From Spell Book UI:
// In render_magic_tab, after tabs
if ui.button("🥥 Ritual").clicked() {
    coconut_ui_state.is_open = !coconut_ui_state.is_open;
}

System Parameter Optimization

Problem

The ui_main_layout system exceeded Bevy's 16-parameter limit after adding magic UI parameters.

Solution

Created a SystemParam group:
#[derive(bevy::ecs::system::SystemParam)]
pub struct UiParams<'w, 's> {
    pub ui_assets: Res<'w, UiAssets>,
    pub interactables: Query<'w, 's, &'static Interactable>,
    pub player_q: Query<'w, 's, (Entity, &'static Skills, Option<&'static ActiveSpell>), With<Player>>,
    pub skills_q: Query<'w, 's, &'static Skills>,
    pub inventory_q: Query<'w, 's, &'static Inventory>,
    pub skills_data: Res<'w, SkillsData>,
}
Usage:
fn ui_main_layout(
    mut contexts: EguiContexts,
    mut ui_state: ResMut<UiState>,
    params: UiParams, // Grouped params
    // ... other individual params
) {
    // Unpack for convenience
    let ui_assets = params.ui_assets;
    let player_q = params.player_q;
    // ...
}
This reduces the parameter count and keeps the system under the limit.

Key Design Decisions

Tab Integration vs Standalone Window

Decision: Spell Book as tab, Coconut UI as window
Rationale:
  • Spell Book is frequently accessed (like Inventory/Stats) → deserves tab
  • Coconut UI is occasional ritual → popup window is appropriate
  • Matches OSRS pattern (spellbook = interface, teleport creation = special interface)

Event-Driven Architecture

All UI interactions dispatch events rather than directly modifying game state:
  • SelectSpellEvent for spell selection
  • PrepareCoconutEvent for coconut charging
Benefits:
  • Decouples UI from game logic
  • Enables networking (events can be serialized)
  • Easier to test and debug

Bija Display in UI

Challenge: Inventory stores ItemId (u32 hash), but UI needs item names
Solution: Hardcoded known bija list in Coconut UI:
let known_bijas = [
    "air_bija", "water_bija", "earth_bija", "fire_bija",
    "chandra_bija", "hanuman_bija", "vishnu_bija", "indra_bija",
    "rahu_bija", "kali_bija", "yama_bija", "rudra_bija"
];
Future Improvement: Could iterate items_db and filter by item type/category.