CraftStation Tutorial 3: UI

This tutorial continues from CraftStation Tutorial 2: Nodes and will guide you through adding a complete crafting UI to the furnace and workbench scenes you already created. You’ll enhance the existing scenes with visual interfaces that provide real-time feedback and user controls.

Prerequisites

Before starting this tutorial, make sure you have:

Building Upon Tutorial 2

We’ll be enhancing the scenes you already created in Tutorial 2:

  • FurnaceTest scene with its furnace station type and iron smelting

Instead of creating new scenes, we’ll add UI elements to the existing FurnaceTest scene to create a complete crafting experience.

Adding UI to the Furnace Scene

Step 1: Open the FurnaceTest Scene

  1. Open your FurnaceTest scene from Tutorial 2

  2. Current Structure (from Tutorial 2):

    FurnaceTest (Node)
    ├── FurnaceStation (CraftStation)
    ├── InputInventory (Inventory)
    └── OutputInventory (Inventory)
    

Step 2: Add UI Structure to Furnace Scene

Add these UI nodes to your FurnaceTest scene:

  1. Add UI Layer

    • CanvasLayer (name: “UI”)

    • MarginContainer (child of UI)

    • VBoxContainer (child of MarginContainer)

  2. Add UI Elements

    Under the VBoxContainer, add:

    • Label (name: “TitleLabel”) - text: “Furnace Station”

    • HBoxContainer (name: “StatusContainer”)

      • Label (name: “InputLabel”) - shows input inventory status

      • Label (name: “OutputLabel”) - shows output inventory status

    • Label (name: “RecipesLabel”) - text: “Available Smelting Recipes:”

    • VBoxContainer (name: “RecipesList”) - for recipe buttons

    • HBoxContainer (name: “ControlsContainer”)

      • Button (name: “AddMaterialsButton”) - text: “Add Materials”

      • Button (name: “SmeltAllButton”) - text: “Smelt All”

    • Label (name: “ProgressLabel”) - shows crafting progress

    • ProgressBar (name: “ProgressBar”)

../_images/craft_station_tutorial_3_nodes.png

Step 3: Update the Furnace Script

Replace the existing script on your FurnaceTest node with this enhanced version that includes UI functionality:

extends Node

 # Existing nodes from Tutorial 2
 @onready var craft_station = $FurnaceStation
 @onready var input_inventory = $InputInventory
 @onready var output_inventory = $OutputInventory

 # New UI nodes
 @onready var input_label = $UI/MarginContainer/VBoxContainer/StatusContainer/InputLabel
 @onready var output_label = $UI/MarginContainer/VBoxContainer/StatusContainer/OutputLabel
 @onready var recipes_list = $UI/MarginContainer/VBoxContainer/RecipesList
 @onready var add_materials_button = $UI/MarginContainer/VBoxContainer/ControlsContainer/AddMaterialsButton
 @onready var smelt_all_button = $UI/MarginContainer/VBoxContainer/ControlsContainer/SmeltAllButton
 @onready var progress_label = $UI/MarginContainer/VBoxContainer/ProgressLabel
 @onready var progress_bar = $UI/MarginContainer/VBoxContainer/ProgressBar

 var recipe_buttons = []

 func _ready():
     # Original setup from Tutorial 2
     _setup_initial_materials()
     _setup_station()

     # New UI setup
     _setup_ui()
     _connect_ui_signals()
     _update_ui()

 func _setup_initial_materials():
     # Add iron ore and coal (from Tutorial 2)
     input_inventory.add("iron_ore", 10)
     input_inventory.add("coal", 5)

 func _setup_station():
     # Connect station signals
     craft_station.on_crafted.connect(_on_craft_completed)
     craft_station.crafting_added.connect(_on_crafting_started)

     # Print station info (from Tutorial 2)
     print("=== FURNACE STATION UI ===")
     print("Station Type: ", craft_station.type.name if craft_station.type else "None")
     print("Available recipes: ", craft_station.valid_recipes.size())

 func _setup_ui():
     # Configure progress bar
     progress_bar.value = 0
     progress_bar.visible = false

     # Create recipe buttons based on valid furnace recipes
     _create_recipe_buttons()

 func _connect_ui_signals():
     # Connect button signals
     add_materials_button.pressed.connect(_add_more_materials)
     smelt_all_button.pressed.connect(_smelt_all_possible)

     # Connect inventory signals for UI updates
     input_inventory.contents_changed.connect(_update_ui)
     output_inventory.contents_changed.connect(_update_ui)

 func _create_recipe_buttons():
     # Clear existing buttons
     for button in recipe_buttons:
         button.queue_free()
     recipe_buttons.clear()

     # Create button for each furnace recipe
     for i in craft_station.valid_recipes.size():
         var recipe_index = craft_station.valid_recipes[i]
         var recipe = craft_station.database.recipes[recipe_index]

         var button = Button.new()
         var product_name = recipe.products[0].item_id if recipe.products.size() > 0 else "Unknown"
         button.text = "Smelt " + product_name

         button.pressed.connect(_craft_recipe.bind(i))
         recipes_list.add_child(button)
         recipe_buttons.append(button)

 func _add_more_materials():
     # Add more smelting materials
     input_inventory.add("iron_ore", 5)
     input_inventory.add("coal", 3)
     print("Added more iron ore and coal")

 func _smelt_all_possible():
     # Try to start smelting if possible
     if craft_station.valid_recipes.size() > 0:
         print("Starting furnace smelting...")
         craft_station.craft(0)

 func _craft_recipe(recipe_list_index: int):
     if recipe_list_index < craft_station.valid_recipes.size():
         print("Starting specific recipe: ", recipe_list_index)
         craft_station.craft(recipe_list_index)

 func _update_ui():
     # Update inventory status
     var input_count = _count_inventory_items(input_inventory)
     var output_count = _count_inventory_items(output_inventory)

     input_label.text = "Input Materials: " + str(input_count) + " items"
     output_label.text = "Output Products: " + str(output_count) + " items"

     # Update recipe button availability
     for i in recipe_buttons.size():
         var button = recipe_buttons[i]
         if i < craft_station.valid_recipes.size():
             var recipe_index = craft_station.valid_recipes[i]
             var recipe = craft_station.database.recipes[recipe_index]
             var can_craft = craft_station.can_craft(recipe)

             button.disabled = not can_craft
             button.modulate = Color.WHITE if can_craft else Color.GRAY

     # Update smelt all button
     smelt_all_button.disabled = not _has_craftable_recipes()

     # Update progress
     _update_progress()

 func _count_inventory_items(inventory: Inventory) -> int:
     var count = 0
     for stack in inventory.stacks:
         if stack:
             count += stack.amount
     return count

 func _has_craftable_recipes() -> bool:
     for recipe_index in craft_station.valid_recipes:
         var recipe = craft_station.database.recipes[recipe_index]
         if craft_station.can_craft(recipe):
             return true
     return false

 func _update_progress():
     if craft_station.craftings.size() > 0:
         var crafting = craft_station.craftings[0]
         var recipe_index = crafting.get_recipe_index()
         var recipe = craft_station.database.recipes[recipe_index]

         var progress = crafting.get_time() / recipe.time_to_craft
         progress = clamp(progress, 0.0, 1.0)

         progress_bar.value = progress * 100
         progress_bar.visible = true

         var product_name = recipe.products[0].item_id if recipe.products.size() > 0 else "Unknown"
         progress_label.text = "Smelting " + product_name + "... " + str(int(progress * 100)) + "%"
     else:
         progress_bar.visible = false
         progress_label.text = "Furnace ready"

 # Enhanced signal handlers
 func _on_crafting_started(crafting_index: int):
     print("Smelting started: ", crafting_index)
     _update_ui()

 func _on_craft_completed(recipe_index: int):
     print("Smelting completed!")

     # Show output (enhanced from Tutorial 2)
     for i in output_inventory.stacks.size():
         var stack = output_inventory.stacks[i]
         if stack:
             print("Produced: ", stack.amount, "x ", stack.item_id)

     _update_ui()

 # Continue updating progress during smelting
 func _process(_delta):
     if craft_station.craftings.size() > 0:
         _update_progress()

 # Keep the space key functionality from Tutorial 2
 func _input(event):
     if event.is_action_pressed("ui_accept"):
         _smelt_all_possible()

Step 4: Test the Furnace UI

  1. Run the FurnaceTest scene

  2. You should see: - “Furnace Station” title - Input/Output inventory counts - “Smelt Iron Ingot” button (enabled if materials available) - “Add Materials” and “Smelt All” buttons - Progress bar and status

  3. Test the functionality: - Click “Add Materials” to add more iron ore and coal - Click “Smelt Iron Ingot” to start smelting - Watch the progress bar fill up - See the input materials decrease and output products appear

Comparing Station Behaviors

Now that both scenes have UI, you can clearly see:

Furnace Station: - Only shows furnace-specific recipes (iron smelting) - Uses iron ore and coal as inputs - Produces metal products

This demonstrates how station types successfully filter recipes and create specialized crafting workflows.

Troubleshooting

UI Not Appearing

If the UI doesn’t show up:

  • Check that CanvasLayer was added correctly

  • Verify UI node names match the script references

  • Ensure the MarginContainer and VBoxContainer hierarchy is correct

Buttons Not Working

If recipe buttons don’t respond:

  • Verify signal connections in the _connect_ui_signals() function

  • Check that _create_recipe_buttons() is being called

  • Ensure the craft station has valid recipes

Progress Not Updating

If the progress bar doesn’t show:

  • Check that _process() is calling _update_progress()

  • Verify crafting signals are connected properly

  • Ensure the progress bar node reference is correct

Different Behavior Between Stations

This is expected! The furnace and workbench should behave differently:

  • Furnace: Only iron smelting recipes, uses ore/coal

If they show the same recipes, check the station type assignments.

Enhancements

Now that you have working UIs for both station types, you can enhance them further:

Visual Improvements: - Add item icons to show materials and products visually - Include inventory slot displays - Add animations for crafting progress

Functional Enhancements: - Implement crafting queues for multiple items - Add drag-and-drop item management - Include recipe cost displays

Station-Specific Features: - Add fuel management for the furnace - Include temperature or power indicators - Create different visual themes for each station type

Conclusion

You have successfully created comprehensive UIs for both crafting stations from Tutorial 2!

  1. Enhanced the existing furnace scene with a specialized smelting interface

  2. Maintained the station type filtering from Tutorial 2

  3. Added real-time progress tracking and material management

  4. Created reusable UI patterns that can be applied to other station types

The tutorials now form a complete progression:

  • Tutorial 1: Resources (items, recipes, station types)

  • Tutorial 2: Nodes (scene setup, scripting, testing)

  • Tutorial 3: UI (visual interfaces, user interaction)

See also