Skip to main content
Nyuron tracks player progress across all minigames through a tier-based achievement system displayed in the Progress HUD (hud_progreso.gd). This system visualizes progression with bronze, silver, gold, and diamond trophies.

Progress HUD Overview

File Location

nyuron/scripts/hud_progreso.gd

Opening the Progress Panel

From the main menu:
scripts/main_menu.gd
@onready var panel_progreso: Control = $CanvasLayer/PanelProgreso
@onready var progress_button := $CanvasLayer/BotonProgreso

func _ready() -> void:
    progress_button.pressed.connect(_on_progress_button_pressed)
    
    # Listen to panel signals
    if panel_progreso.has_signal("back_pressed"):
        panel_progreso.back_pressed.connect(_on_progress_hud_closed)
    if panel_progreso.has_signal("play_game_pressed"):
        panel_progreso.play_game_pressed.connect(_on_progress_hud_play_game)

func _on_progress_button_pressed():
    # Update data before showing
    if panel_progreso.has_method("update_info"):
        panel_progreso.update_info()
    if panel_progreso.has_method("update_logros"):
        panel_progreso.update_logros()
    
    panel_progreso.visible = true
    main_ui_buttons.visible = false
    progress_button.visible = false
    
    # Dim background
    dim_overlay.visible = true
    create_tween().tween_property(dim_overlay, "color:a", 0.6, 0.40)

Achievement Tiers

Score Thresholds

Each minigame has four achievement tiers:
scripts/hud_progreso.gd
var RANGOS_JUEGO := {
    "memorice": { 
        "bronce": 200, 
        "plata": 400, 
        "oro": 600, 
        "diamante": 1000 
    },
    "turtle_runner": { 
        "bronce": 1000, 
        "plata": 2000, 
        "oro": 3000, 
        "diamante": 4000 
    },
    "worm_catch": { 
        "bronce": 60, 
        "plata": 80, 
        "oro": 100, 
        "diamante": 115 
    },
    "food_catch": { 
        "bronce": 1000, 
        "plata": 2000, 
        "oro": 2500, 
        "diamante": 3000 
    },
    "counting_animals": { 
        "bronce": 50, 
        "plata": 150, 
        "oro": 300, 
        "diamante": 500 
    },
    "nyuron_color": { 
        "bronce": 8, 
        "plata": 12, 
        "oro": 16, 
        "diamante": 20 
    }
}

Trophy Icons

func icono_por_score(game_key: String, score: int) -> String:
    if not RANGOS_JUEGO.has(game_key):
        return "res://assets/bronze.png"
    
    var rangos = RANGOS_JUEGO[game_key]
    
    if score >= rangos["diamante"]:
        return "res://assets/diamond.png"
    elif score >= rangos["oro"]:
        return "res://assets/gold.png"
    elif score >= rangos["plata"]:
        return "res://assets/silver.png"
    elif score >= rangos["bronce"]:
        return "res://assets/bronze.png"
    else:
        return "res://assets/bronze.png"  # Default to bronze

Progress Cards

Card Structure

Each minigame has a card showing:
  • Game name
  • Trophy icon (based on high score)
  • Progress bar to next tier
  • “Play” button to launch the minigame
func update_logros():
    var scores = {}
    var cfg = ConfigFile.new()
    if cfg.load("user://high_scores.cfg") == OK:
        for key in cfg.get_section_keys("high_scores"):
            scores[key] = int(cfg.get_value("high_scores", key, 0))
    
    for card in vbox_logros.get_children():
        if not (card is Panel):
            continue
        
        # Map card name to game key
        var game_key = ""
        var button_name = ""
        
        match card.name:
            "Card_TurtleRun":
                game_key = "turtle_runner"; button_name = "TurtleButton"
            "Card_WormBucket":
                game_key = "worm_catch"; button_name = "WormButton"
            "Card_FoodCatch":
                game_key = "food_catch"; button_name = "FoodButton"
            "Card_Memorice":
                game_key = "memorice"; button_name = "MemoriceButton"
            "Card_CountingAnimals":
                game_key = "counting_animals"; button_name = "CountingButton"
            "Card_NyuronColor":
                game_key = "nyuron_color"; button_name = "ColorButton"
        
        if game_key == "" or button_name == "":
            continue
        
        # Get UI elements
        var label_nombre: Label = card.get_node("HBox_Main/VBox_Info/HBoxHeader/LabelNombre")
        var icono: TextureRect = card.get_node("HBox_Main/MarginContainer/TextureRectLogro")
        var boton: Button = card.get_node("HBox_Main/VBox_Info/HBoxHeader/" + button_name)
        var progress_bar: ProgressBar = card.get_node("HBox_Main/VBox_Info/ProgressBar")
        var progress_label: Label = card.get_node("HBox_Main/VBox_Info/ProgressBar/ProgressLabel")
        
        # Update content
        var score = scores.get(game_key, 0)
        label_nombre.text = nombre_juego_legible(game_key)
        
        var progress_info = _get_progress_info(game_key, score)
        
        # Configure progress bar
        if progress_info.next_trophy == "none":
            progress_bar.visible = false
            progress_label.text = "N/A"
        elif progress_info.next_trophy == "max":
            # Maximum tier reached
            progress_bar.visible = true
            progress_bar.min_value = 0
            progress_bar.max_value = progress_info.max
            progress_bar.value = score
            progress_label.text = "¡Máximo! (%d)" % score
        else:
            # Show progress to next tier
            progress_bar.visible = true
            progress_bar.min_value = progress_info.min
            progress_bar.max_value = progress_info.max
            progress_bar.value = score
            progress_label.text = "%d / %d" % [score, progress_info.max]
        
        # Update trophy icon
        icono.texture = load(icono_por_score(game_key, score))
        
        # Connect play button
        if not boton.is_connected("pressed", Callable(self, "_on_jugar_pressed")):
            boton.connect("pressed", Callable(self, "_on_jugar_pressed").bind(game_key))

Progress Calculation

func _get_progress_info(game_key: String, score: int) -> Dictionary:
    if not RANGOS_JUEGO.has(game_key):
        return { "min": 0, "max": 100, "current": 0, "next_trophy": "none" }
    
    var rangos = RANGOS_JUEGO[game_key]
    
    if score < rangos["bronce"]:
        return { 
            "min": 0, 
            "max": rangos["bronce"], 
            "current": score, 
            "next_trophy": "bronce" 
        }
    elif score < rangos["plata"]:
        return { 
            "min": rangos["bronce"], 
            "max": rangos["plata"], 
            "current": score, 
            "next_trophy": "plata" 
        }
    elif score < rangos["oro"]:
        return { 
            "min": rangos["plata"], 
            "max": rangos["oro"], 
            "current": score, 
            "next_trophy": "oro" 
        }
    elif score < rangos["diamante"]:
        return { 
            "min": rangos["oro"], 
            "max": rangos["diamante"], 
            "current": score, 
            "next_trophy": "diamante" 
        }
    else:
        return { 
            "min": 0, 
            "max": rangos["diamante"], 
            "current": score, 
            "next_trophy": "max" 
        }
Example: If a player has 75 points in Worm Catch:
  • Current tier: Bronze (60+)
  • Next tier: Silver (80)
  • Progress: 75/80
  • Progress bar shows: min=60, max=80, value=75

Player Info Display

Loading Player Name

@onready var label_jugador: Label = $LabelJugador
@onready var label_progreso: Label = $LabelProgreso

func update_info():
    var config = ConfigFile.new()
    if config.load("user://player_data.cfg") == OK:
        var name = config.get_value("player", "name", "Jugador")
        label_jugador.text = "Jugador: " + str(name)
    
    label_progreso.text = "Progreso en los minijuegos:"

Launching Minigames from Progress Panel

Signal Emission

signal play_game_pressed(game_key: String)
signal back_pressed

func _on_jugar_pressed(game_key: String):
    play_game_pressed.emit(game_key)
scripts/main_menu.gd
func _on_progress_hud_play_game(game_key: String):
    # Hide progress panel
    panel_progreso.visible = false
    main_ui_buttons.visible = true
    progress_button.visible = true
    dim_overlay.visible = false
    
    # Launch corresponding minigame
    match game_key:
        "turtle_runner": _on_turtle_pressed()
        "worm_catch": _on_worm_pressed()
        "food_catch": _on_food_pressed()
        "memorice": _on_memorice_pressed()
        "counting_animals": _on_counting_pressed()
        "nyuron_color": _on_color_pressed()

Human-Readable Game Names

func nombre_juego_legible(game_key: String) -> String:
    match game_key:
        "turtle_runner": return "Carrera de Tortugas"
        "worm_catch": return "Caza de Gusanos"
        "food_catch": return "Atrapa Comida"
        "memorice": return "Memorice Marino"
        "counting_animals": return "Conteo Marino"
        "nyuron_color": return "Nyuron Canta"
        _: return game_key

UI Behavior

Panel Overlay

The progress panel dims the background when open:
scripts/main_menu.gd
@onready var dim_overlay: ColorRect = $CanvasLayer/FondoOscuro

func _on_progress_button_pressed():
    # ...
    dim_overlay.visible = true
    create_tween().tween_property(dim_overlay, "color:a", 0.6, 0.40)

func _on_progress_hud_closed():
    panel_progreso.visible = false
    main_ui_buttons.visible = true
    progress_button.visible = true
    
    if not menu_panel.visible:
        var tween = create_tween()
        tween.tween_property(dim_overlay, "color:a", 0.0, 0.25)
        tween.tween_callback(func(): dim_overlay.visible = false)

Clicking Outside to Close

dim_overlay.gui_input.connect(_on_dim_overlay_clicked)

func _on_dim_overlay_clicked(event: InputEvent) -> void:
    if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed():
        if panel_progreso.visible:
            _on_progress_hud_closed()

Complete Progress Card Example

Scenario: Player has 2100 points in Turtle Runner
  1. Load score: scores["turtle_runner"] = 2100
  2. Determine tier: 2100 >= 2000 (silver) → Silver trophy
  3. Calculate progress:
    • Current tier: Silver (2000+)
    • Next tier: Gold (3000)
    • Progress: 2100 / 3000
  4. Update UI:
    • Trophy icon: res://assets/silver.png
    • Progress bar: min=2000, max=3000, value=2100
    • Label: “2100 / 3000”
  5. Play button: Clicking launches _on_turtle_pressed()

Progress Bar States

State 1: Below Bronze

Score: 30 / 60
Icon: Bronze (grayed or default)
Bar: 0 → 60 (value: 30)
Label: "30 / 60"

State 2: Bronze to Silver

Score: 70 / 80
Icon: Bronze
Bar: 60 → 80 (value: 70)
Label: "70 / 80"

State 3: Maximum Tier

Score: 120
Icon: Diamond
Bar: 0 → 115 (value: 120, exceeds max)
Label: "¡Máximo! (120)"

Initialization Flow

func _ready():
    update_info()    # Load player name
    update_logros()  # Build all progress cards
Called automatically when the scene loads, and manually when the main menu opens the panel:
scripts/main_menu.gd
func _on_progress_button_pressed():
    if panel_progreso.has_method("update_info"):
        panel_progreso.update_info()
    if panel_progreso.has_method("update_logros"):
        panel_progreso.update_logros()
    # ...

Adding New Minigames to Progress Tracking

Step 1: Add Score Thresholds

var RANGOS_JUEGO := {
    # ... existing games ...
    "your_new_game": {
        "bronce": 100,
        "plata": 250,
        "oro": 500,
        "diamante": 1000
    }
}

Step 2: Add Card Mapping

func update_logros():
    # ...
    match card.name:
        # ... existing mappings ...
        "Card_YourNewGame":
            game_key = "your_new_game"; button_name = "YourNewGameButton"

Step 3: Add Human-Readable Name

func nombre_juego_legible(game_key: String) -> String:
    match game_key:
        # ... existing names ...
        "your_new_game": return "Your New Game Title"
        _: return game_key

Step 4: Add Launch Handler

scripts/main_menu.gd
func _on_progress_hud_play_game(game_key: String):
    # ...
    match game_key:
        # ... existing cases ...
        "your_new_game": _on_your_new_game_pressed()

Step 5: Register in ScoreManager

ScoreManager.gd
var high_scores = {
    # ... existing games ...
    "your_new_game": 0
}

Best Practices

Balanced Thresholds

Set thresholds based on typical gameplay:
  • Bronze: Achievable in first try (20-30% of high scores)
  • Silver: Requires practice (50% of high scores)
  • Gold: Skilled players (75% of high scores)
  • Diamond: Mastery (95th percentile)

Visual Feedback

The progress bar provides clear goals:
  • Players see exactly how many points until next tier
  • Full bar at max tier shows achievement completion
  • Incremental progress motivates continued play

Data Integrity

if not RANGOS_JUEGO.has(game_key):
    return { "min": 0, "max": 100, "current": 0, "next_trophy": "none" }
Always handle missing/invalid game keys gracefully.

Build docs developers (and LLMs) love