Skip to main content

Overview

The Worm Bucket minigame is a time-based catching game where players shoot a claw to catch worms before time runs out. Bad worms cause stuns, and captured worms can escape from the bucket. Script Path: minigames/worm_bucket/scripts/main.gd Extends: Node2D

Exported Variables

None - all configuration is handled through scene references and constants.

@onready Node References

UI Elements

ui_score
Label
Displays current score (worms caught)
ui_time
Label
Displays remaining time in seconds (formatted as “XX.XX s”)
intro_panel
Control
Initial instruction panel
intro_button
Button
Start game button in intro panel
backButton
Button
Pause button during gameplay

Game Over Panel

game_over_panel
Panel
Game over/pause overlay
title_lbl
Label
Panel title (“Fin del Juego” or “Pausa”)
score_lbl
Label
Final score in game over panel
back_btn
TextureButton
Return to main menu button
retry_btn
TextureButton
Restart game button

Game Objects

game_timer
Timer
60-second countdown timer. Triggers _on_game_over() when it reaches zero.
escape_timer
Timer
Periodic timer that triggers worm escapes from bucket. Fires every few seconds.
nyuron
Node
The Nyuron character node with claw shooting mechanics
spawner
Node
Worm spawner that emits spawned signal when creating new worms
bucket
Node2D
The bucket where caught worms are stored
bucket_sprite
AnimatedSprite2D
Animated sprite showing bucket fill states (0-5+ worms)
damage_border
ColorRect
Red border overlay for visual feedback when hitting bad worms
damage_mat
ShaderMaterial
Shader material for damage pulse effect

Audio

sfx_catch
AudioStreamPlayer
Sound effect played when catching a worm (pitch varies 0.9-1.1)
sfx_fail
AudioStreamPlayer
Sound effect played when catching a bad worm

State Variables

score
int
Current score (number of worms caught). Decreases by 1 when a worm fully escapes.
bucket_count
int
Number of worms currently in the bucket. Affects bucket sprite animation.
is_stunned
bool
Whether player is currently stunned (from catching bad worm). Blocks input for 2 seconds.
is_paused
bool
Whether game is paused
last_coins_gained
int
Coins earned in current session (for game over display)
damage_tween
Tween
Reference to the damage pulse animation tween

Signals

back_to_menu
signal
Emitted when navigating back to main menu

Core Functions

Game Initialization

_ready
void
Initializes the game:
  • Connects spawner, timer, and button signals
  • Sets up UI process modes for pause handling
  • Adds self to “worm_game” group
  • Shows intro panel
func _ready() -> void:
    add_to_group("worm_game")
    
    spawner.spawned.connect(_on_spawner_spawned)
    game_timer.timeout.connect(_on_game_over)
    escape_timer.timeout.connect(_on_escape_timer)
    retry_btn.pressed.connect(_on_retry_pressed)
    back_btn.pressed.connect(_on_back_pressed)
    
    if backButton:
        backButton.pressed.connect(_on_backButton_pressed)
    
    var ui_node := $UI
    if ui_node is CanvasLayer:
        ui_node.follow_viewport_enabled = true
    
    game_over_panel.process_mode = Node.PROCESS_MODE_ALWAYS
    game_over_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
    back_btn.mouse_filter = Control.MOUSE_FILTER_STOP
    backButton.process_mode = Node.PROCESS_MODE_ALWAYS
    
    game_over_panel.visible = false
    
    if bucket_sprite:
        bucket_sprite.stop()
    
    _update_ui()
    _show_intro()
_show_intro
void
Shows intro panel and pauses all game systems:
  • Sets is_paused = true
  • Pauses game_timer, escape_timer, and spawner
  • Disables worm input
  • Hides back button
_start_game
void
Starts the game when intro button pressed:
  • Hides intro panel
  • Sets is_paused = false
  • Unpauses all timers
  • Enables worm input
  • Starts 60-second countdown

Main Loop

_process
void
Updates timer display each frame:
func _process(_delta: float) -> void:
    if is_paused:
        return
    
    var t_left: float = max(0.0, game_timer.time_left)
    ui_time.text = "%.2f s" % t_left

Spawner Integration

_on_spawner_spawned
void
Called when spawner creates a new worm. Sets up worm connections.Parameters:
  • worm (Node): The spawned worm instance
Setup:
  • Assigns game reference to worm
  • Connects request_catch and escaped signals
  • Connects input_event for click detection
  • Sets input_pickable = true
func _on_spawner_spawned(worm: Node) -> void:
    if "game" in worm:
        worm.game = self
    
    if worm.has_signal("request_catch"):
        worm.request_catch.connect(_on_worm_request_catch)
    if worm.has_signal("escaped"):
        worm.escaped.connect(_on_worm_escaped)
    
    if worm is Area2D:
        worm.input_pickable = true
        if worm.input_event.is_connected(_on_worm_input_event):
            worm.input_event.disconnect(_on_worm_input_event)
        
        worm.input_event.connect(_on_worm_input_event.bind(worm))
_on_worm_input_event
void
Handles mouse clicks on worms. Calls _on_worm_request_catch() when left-clicked.Parameters:
  • _viewport (Viewport)
  • event (InputEvent)
  • _shape_idx (int)
  • worm (Node)

Worm Escape System

_on_escape_timer
void
Called periodically to make worms escape from bucket:
  • Decreases bucket_count by 1 (min 0)
  • Spawns escaped worm with start_escape() behavior
  • Worm starts at bucket position and swims away
func _on_escape_timer() -> void:
    if is_paused:
        return
    
    if bucket_count <= 0:
        return
    
    bucket_count = max(0, bucket_count - 1)
    _update_ui()
    
    var w = spawner.worm_scene.instantiate()
    $Playfield.add_child(w)
    
    if "game" in w:
        w.game = self
    
    if w.has_method("start_escape"):
        w.start_escape(bucket.global_position)
    
    # Connect signals...

Worm Catching

_on_worm_request_catch
void
Main catch logic triggered when player clicks a worm.Parameters:
  • worm (Node): The worm being caught
Bad Worm Logic:
  • Checks worm.is_bad property
  • Plays fail sound
  • Sets is_stunned = true for 2 seconds
  • Calls nyuron.stun(2.0)
  • Starts damage pulse visual effect
  • Removes worm from scene
Normal Worm Logic:
  • Calls nyuron.shoot_to(worm_position, is_recapture)
  • is_recapture is true if worm state is “ESCAPING”
  • Waits for claw_reached_target signal
  • Attaches worm to claw tip
  • Waits for shot_finished signal
  • Calls worm.detach_and_go_to_bucket()
  • Increments score by 1 (only if not recapture)
  • Increments bucket_count by 1
  • Plays catch sound with pitch variation
func _on_worm_request_catch(worm: Node) -> void:
    if is_paused:
        return
    if is_stunned:
        return
    
    # Bad worm logic
    if "is_bad" in worm and worm.is_bad:
        if sfx_fail:
            sfx_fail.play()
        
        is_stunned = true
        if nyuron.has_method("stun"):
            nyuron.stun(2.0)
        _start_damage_pulse()
        
        get_tree().create_timer(2.0).timeout.connect(func ():
            is_stunned = false
            _stop_damage_pulse()
        )
        
        if is_instance_valid(worm):
            worm.queue_free()
        return
    
    # Normal worm catching with claw mechanics...
    var is_recapture: bool = ("state" in worm and worm.state == "ESCAPING")
    
    if nyuron.has_method("shoot_to"):
        nyuron.shoot_to(worm.global_position, is_recapture)
    # ... signal connections for claw animation
_on_worm_escaped
void
Called when a worm fully escapes off-screen:
  • Decreases score by 1 (min 0)
  • Updates UI
  • Prints escape message
Parameters:
  • _worm (Node): The escaped worm

Game Over

_on_game_over
void
Called when 60-second timer expires:
  • Stops spawner and escape timer
  • Updates time label to “Fin. Puntos: X”
  • Saves high score via ScoreManager
  • Calculates coins: score × 2.5
  • Disables all worm input
  • Shows game over panel
func _on_game_over() -> void:
    if is_paused:
        return
    
    $Playfield/Spawner/SpawnTimer.stop()
    escape_timer.stop()
    
    ui_time.text = "Fin. Puntos: %d" % score
    
    if score > 0:
        var score_manager = get_node("/root/ScoreManager")
        if score_manager:
            score_manager.save_high_score("worm_catch", score)
            var coins_earned = int(score * 2.5)
            if coins_earned > 0:
                score_manager.add_coins(coins_earned)
                print("Ganaste:", coins_earned, "monedas")
                last_coins_gained = coins_earned
    
    _show_game_over_panel()
    
    for worm in $Playfield.get_children():
        if worm is Area2D:
            worm.input_pickable = false
_show_game_over_panel
void
Displays the game over panel with final score and coins earned.
_on_back_pressed
void
Returns to main menu. Resumes game if paused first.
_on_retry_pressed
void
Reloads current scene to restart game.
_on_backButton_pressed
void
Toggles pause when back button pressed during gameplay.

UI Updates

_update_ui
void
Updates score label and bucket sprite animation:Bucket States:
  • 0 worms: “balde vacio”
  • 1 worm: “con 1 gusano”
  • 2 worms: “con 2 gusano”
  • 3 worms: “con 3 gusano”
  • 4 worms: “con 4 gusano”
  • 5+ worms: “balde lleno”
func _update_ui() -> void:
    ui_score.text = "Puntos: %d" % score
    
    if bucket_sprite:
        match bucket_count:
            0: bucket_sprite.play("balde vacio")
            1: bucket_sprite.play("con 1 gusano")
            2: bucket_sprite.play("con 2 gusano")
            3: bucket_sprite.play("con 3 gusano")
            4: bucket_sprite.play("con 4 gusano")
            _: bucket_sprite.play("balde lleno")

Visual Effects

_start_damage_pulse
void
Starts looping red border pulse effect when bad worm is caught:
  • Tweens shader intensity 0.0 → 0.4 → 0.0
  • Loop continues until _stop_damage_pulse() called
  • 0.2s per pulse phase
_stop_damage_pulse
void
Stops damage pulse tween and resets shader intensity to 0.0

Pause System

pause_game
void
Pauses the game:
  • Pauses game_timer and escape_timer
  • Pauses spawner timer
  • Disables Nyuron processing and animation
  • Calls pause_all_worms(true)
  • Stops bucket sprite animation
  • Kills damage tween if active
  • Shows game over panel with “Pausa” title
func pause_game():
    print("Pausando juego...")
    is_paused = true
    
    game_timer.paused = true
    escape_timer.paused = true
    if spawner and spawner.has_node("SpawnTimer"):
        spawner.get_node("SpawnTimer").paused = true
    
    if nyuron:
        nyuron.process_mode = Node.PROCESS_MODE_DISABLED
        var nyuron_anim := nyuron.get_node_or_null("AnimatedSprite2D")
        if nyuron_anim:
            nyuron_anim.speed_scale = 0.0
    
    pause_all_worms(true)
    
    if bucket_sprite:
        bucket_sprite.speed_scale = 0.0
    
    if damage_tween:
        damage_tween.kill()
    
    game_over_panel.visible = true
    title_lbl.text = "Pausa"
    score_lbl.text = "Puntos: %d" % score
    back_btn.visible = true
resume_game
void
Resumes from pause:
  • Unpauses all timers
  • Re-enables Nyuron and worms
  • Resumes animations
  • Hides game over panel
pause_all_worms
void
Helper to pause/unpause all worms in Playfield.Parameters:
  • pause (bool): If true, disables worm processing and sets animation speed to 0.0. If false, re-enables.
func pause_all_worms(pause: bool):
    var worm_count = 0
    for worm in $Playfield.get_children():
        if is_instance_valid(worm) and worm is Area2D:
            if pause:
                worm.process_mode = Node.PROCESS_MODE_DISABLED
                var worm_anim := worm.get_node_or_null("AnimatedSprite2D")
                if worm_anim:
                    worm_anim.speed_scale = 0.0
            else:
                worm.process_mode = Node.PROCESS_MODE_INHERIT
                var worm_anim := worm.get_node_or_null("AnimatedSprite2D")
                if worm_anim:
                    worm_anim.speed_scale = 1.0
            worm_count += 1

Scene Structure Requirements

The scene must include:
  • Nyuron character node with shoot_to(), stun() methods and claw_reached_target, shot_finished signals
  • Spawner node with spawned signal and worm_scene property
  • Bucket node (Node2D) with animated sprite showing fill states
  • Playfield node (container for spawned worms)
  • UI elements: score label, timer label, damage border with shader
  • Game timer (60s) and escape timer
  • Intro and game over panels

Worm Bucket Guide

User guide for the Worm Bucket minigame

ScoreManager API

High score and coin management system

Build docs developers (and LLMs) love