Crafting Compulsion: Building Dynamic Crafting Systems in Godot

Posted by Gemma Ellison
./
July 14, 2025

It’s time to stop building static worlds and start empowering players! A well-designed crafting system isn’t just a feature; it’s a dynamic engine for exploration, resource management, and player agency. Forget simple fetch quests – we’re diving into building a crafting system that makes players feel like true artisans in your Godot game. This tutorial isn’t about copy-pasting; it’s about understanding the core concepts and building a foundation for a truly unique crafting experience.

Resource Gathering with Collision Detection

Collision detection is the backbone of resource gathering. I believe in a proactive approach: don’t just check for collisions after the player moves. Instead, use a KinematicBody2D (or 3D equivalent) for both the player and your resources. This gives you precise control.

First, create a ResourceNode scene (e.g., for wood). Attach a CollisionShape2D representing the resource’s pickup area. Then, add this script:

extends KinematicBody2D

export var resource_type: String = "wood"  # Define what resource this is
export var quantity: int = 1

func _physics_process(delta: float) -> void:
    var collision = move_and_collide(Vector2.ZERO) #Required to get collisions, even when static
    if collision and collision.collider.is_in_group("Player"):
        var player = collision.collider
        player.add_resource(resource_type, quantity)
        queue_free()  # Remove the resource from the scene

The pitfall: Forgetting the move_and_collide(Vector2.ZERO) is a common mistake. This seemingly pointless function is crucial to trigger the collision detection, even though the resource node is not actually moving.

Pro Tip: Use Godot’s groups to easily identify the player. Add your player node to a group named "Player". This makes collision checking much cleaner and less prone to errors.

Inventory Management with Arrays

Arrays are simple, but powerful for inventory. I argue that starting with a simple array-based inventory allows you to understand the fundamentals before moving to more complex data structures.

# Player script
var inventory: Array = []
var max_inventory_size: int = 10

func add_resource(resource_type: String, quantity: int) -> void:
  # Check if there's space in the inventory.
  if inventory.size() < max_inventory_size:
    # Check if the resource already exists in the inventory
    var found = false
    for item in inventory:
      if item["type"] == resource_type:
        item["quantity"] += quantity
        found = true
        break
    # If the resource doesn't exist, add a new entry
    if not found:
      inventory.append({"type": resource_type, "quantity": quantity})
    update_inventory_ui()  # Update the UI to reflect changes

  else:
      print("Inventory is full!")

Challenge: Preventing overflow. Make sure max_inventory_size is enforced. Consider adding a “drop” functionality when the inventory is full.

Opinion: Start with a simple array, then refactor to a dictionary or a custom InventoryItem class when your game’s complexity demands it. Premature optimization is the root of all evil!

Crafting Recipes with Dictionaries

Dictionaries shine when defining crafting recipes. I maintain that a dictionary-based recipe system is the most flexible and readable way to manage crafting logic.

var crafting_recipes: Dictionary = {
    "axe": {
        "ingredients": {"wood": 5, "stone": 2},
        "result": {"axe": 1}
    },
    "pickaxe": {
        "ingredients": {"wood": 5, "stone": 3},
        "result": {"pickaxe": 1}
    }
}

func craft_item(item_name: String) -> void:
    if crafting_recipes.has(item_name):
        var recipe = crafting_recipes[item_name]
        if can_craft(recipe["ingredients"]):
            remove_ingredients(recipe["ingredients"])
            add_item_to_inventory(recipe["result"])
            update_inventory_ui()
        else:
            print("Not enough resources!")
    else:
        print("Recipe not found!")

func can_craft(ingredients: Dictionary) -> bool:
    for resource in ingredients:
        if !has_resource(resource, ingredients[resource]):
            return false
    return true

func has_resource(resource_type: String, quantity: int) -> bool:
    for item in inventory:
        if item["type"] == resource_type:
            return item["quantity"] >= quantity
    return false

func remove_ingredients(ingredients: Dictionary) -> void:
  for resource in ingredients:
    for i in range(inventory.size()):
        if inventory[i]["type"] == resource:
            inventory[i]["quantity"] -= ingredients[resource]
            if inventory[i]["quantity"] <= 0:
                inventory.remove(i)  # Remove the item from inventory if quantity reaches 0
            break

Common Mistake: Accidentally modifying the original crafting_recipes dictionary when crafting. Make a copy of the recipe before modifying it.

Insight: Implement a check to avoid crafting items the player can’t hold. You could make them drop an item in the inventory when crafting and the inventory is full.

Building a Functional Crafting UI

The UI is the bridge between the player and the crafting system. I advocate for a clear, intuitive interface that provides immediate feedback.

  1. Create a CanvasLayer: This ensures the UI stays on top of the game world.
  2. Add a Panel: Provides a background for the crafting menu.
  3. Use GridContainer or HBoxContainer: Arrange crafting recipe buttons.
  4. Connect Button Signals: When a button is pressed, call the craft_item function with the corresponding item name.
  5. Dynamically Populate the UI: Loop through the crafting_recipes dictionary to create buttons for each recipe.

Step-by-step example:

a. Add a Button node as a child of your GridContainer. b. Set the button’s text to the item name (e.g., “Axe”). c. Connect the pressed signal of the button to a function in your UI script. d. In that function, call craft_item("axe").

Important: Update the UI every time the inventory changes! This requires connecting the inventory modification functions (add_resource, remove_ingredients) to a UI update function.

Pitfall: Neglecting to update the UI after crafting. Players need immediate visual confirmation that their actions have succeeded.

By carefully considering these elements and implementing them with a clear understanding of the underlying principles, you can create a crafting system that elevates your Godot game from a simple experience to a compelling and engaging world. Forget simple additions; craft a new dynamic.