Hello. I was wondering if anyone knew how to add item combination in c#. May I please get an example on how to do this using one cloth + one stick = one flag as the example? Thank you for your time and have a nice day.

-SK

Well, you’d build a list of recepies. Each recepie should have a list of ingredients and at least one output item. A not so naive solution would most likely deal with stacks, bundles or groups of items, such as in the case like 4 cloth + 2 strings = 1 sail. You’d have to handle lists of items to represent your inventory to see what you can craft, as well as adding and subtracting items from it when you actually craft. As you can imagine, it can grow out of hand pretty quickly.

I made an attempt to make some basic system that supports crafting different items. It’s far from perfect, I am sure, but it’s a starting point. Some code design goals I had was to keep it simple to use from the end user perspective.

There are four main classes to consider in my example.

  • RecepieDatabase
  • Recepie
  • ItemSet
  • ItemBundle

RecepieDatabase represents a collection of Recepies. You can have one or several databases if you want, it doesn’t matter that much. You can Add, Create, Get, Craft Recepies from here, as well as query for craftable recepies based on what you have in your inventory.

Recepie represents a Recepie with a bill of materials (ingredients) and an output. It uses an ItemSet to keep track of all different kinds of ingredients that is needed to produce an output item.

ItemSet is a collection of several ItemBundles. To simplify what I mean by that, imagine it is a class that lets you store 20 stones, 10 keyboards and 2 action figures. Whatever you want. I used it to represent my inventory and ingredients in recepies.

ItemBundle is a bundle of a particular item. 20 stones is one ItemBundle. 1 car is another ItemBundle. It basically is just “something” and “how many of that kind?”

You can copy paste the code below into CraftableSystemDemo.cs, add the component to a game object, hit play and observe the output in the console. I’ll include the output after the code for the lazy.

using System;
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using System.Text;

public class CraftableSystemDemo : MonoBehaviour
{
    private RecepieDatabase database = new RecepieDatabase();
    private ItemSet inventory = new ItemSet();
    private StringBuilder log = new StringBuilder();

    void Awake()
    {
        try
        {
            CreateRecepies();
            PopulateInventory();

            // Display what we got at the moment.
            LogCraftableRecepies();
            LogInventory();

            Craft("hammer");
            Craft("heavy hammer");
        }
        finally
        {
            PrintLog();
        }
    }

    private void CreateRecepies()
    {
        database
            .Create("flag")
            .Require("cloth")
            .Require("stick");

        database
            .Create("hammer")
            .Require("stone")
            .Require("stick");

        database
            .Create("heavy hammer")
            .Require("stone")
            .Require("hammer");

        database
            .Create("answer")
            .Require("keyboard")
            .Require("dedication", 10)
            .Require("code", 5);
    }

    private void PopulateInventory()
    {
        inventory.SetAmount("cloth", 2);
        inventory.SetAmount("stick", 2);
        inventory.SetAmount("stone", 2);
    }

    private void Craft(string item)
    {
        Log("Crafting " + item);
        Log();

        database.Craft(item, inventory);

        // Could be helpful to see what's changed.
        LogInventory();
        LogCraftableRecepies();
    }

    private void LogCraftableRecepies()
    {
        Log("Craftable Recepies");
        Log("==================");

        foreach (var recepie in database.GetCraftableRecepies(inventory))
            log.AppendLine("  " + recepie.ToString());

        Log();
    }

    private void LogInventory()
    {
        Log("Inventory Contents");
        Log("==================");

        foreach (ItemBundle bundle in inventory.Bundles)
            log.AppendLine("  " + bundle.ToString());

        Log();
    }

    private void Log(string message = "")
    {
        log.AppendLine(message);
    }

    private void PrintLog()
    {
        // It got tedious to see callstacks of all steps so I put it all in one StringBuilder...
        print(log);
    }
}

public class RecepieDatabase
{
    Dictionary<string, Recepie> recepies = new Dictionary<string, Recepie>();

    public void Add(Recepie recepie)
    {
        recepies.Add(recepie.Name, recepie);
    }

    public Recepie Create(string item, int amount = 1)
    {
        Recepie recepie = Recepie.For(item, amount);
        Add(recepie);
        return recepie;
    }

    public Recepie Get(string recepie)
    {
        return recepies[recepie];
    }

    public IEnumerable<Recepie> GetCraftableRecepies(ItemSet availableItems)
    {
        foreach (var recepie in recepies.Values)
            if (recepie.CanCraft(availableItems))
                yield return recepie;
    }

    public void Craft(string recepie, ItemSet inventory)
    {
        inventory.AddItem(Get(recepie).Craft(inventory));
    }

    public bool CanCraft(string recepie, ItemSet inventory)
    {
        return Get(recepie).CanCraft(inventory);
    }
}

public class ItemSet
{
    Dictionary<string, ItemBundle> bundles = new Dictionary<string, ItemBundle>();

    public IEnumerable<ItemBundle> Bundles { get { return bundles.Values; } }

    public void AddItem(ItemBundle item)
    {
        ChangeAmount(item.Name, item.Amount);
    }

    public void ChangeAmount(string item, int amount)
    {
        if (amount == 0)
            return;

        ItemBundle bundle = null;
        if (!bundles.TryGetValue(item, out bundle))
            CreateBundle(item, amount);
        else
        {
            bundle.Change(amount);
            Prune(bundle);
        }
    }

    public int GetAmount(string item)
    {
        ItemBundle bundle = null;
        if (!bundles.TryGetValue(item, out bundle))
            return 0;
        else
            return bundle.Amount;
    }

    public void SetAmount(string item, int amount)
    {
        if (amount == 0)
        {
            Remove(item);
            return;
        }

        ItemBundle bundle = null;
        if (!bundles.TryGetValue(item, out bundle))
            bundle = CreateBundle(item, amount);
        else
            bundle.Set(amount);

        Prune(bundle);
    }

    public bool Contains(ItemSet items)
    {
        return items.Bundles.All(item => GetAmount(item.Name) >= item.Amount);
    }

    public void Remove(string item)
    {
        bundles.Remove(item);
    }

    public void Remove(ItemSet items)
    {
        if (!Contains(items))
            throw new ArgumentException("Can't remove items because not all of them exist in this ItemSet", "items");

        foreach (var item in items.Bundles)
            ChangeAmount(item.Name, -item.Amount);
    }

    private void Prune(ItemBundle bundle)
    {
        if (bundle.IsEmpty)
            Remove(bundle.Name);
    }

    private ItemBundle CreateBundle(string item, int amount)
    {
        ItemBundle bundle = new ItemBundle(item, amount);
        bundles.Add(item, bundle);
        return bundle;
    }

    public override string ToString()
    {
        return string.Join(", ", Bundles.Select(bundle => bundle.ToString()).ToArray());
    }
}

public class Recepie
{
    private readonly ItemBundle output;
    private readonly ItemSet ingredients;

    public string Name { get { return output.Name; } }
    public int Amount { get { return output.Amount; } }

    public Recepie(string item, int amount)
    {
        output = new ItemBundle(item, amount);
        ingredients = new ItemSet();
    }

    public static Recepie For(string item, int amount = 1)
    {
        return new Recepie(item, amount);
    }

    public Recepie Require(string item, int amount = 1)
    {
        ingredients.ChangeAmount(item, amount);
        return this;
    }

    public bool CanCraft(ItemSet availableItems)
    {
        return availableItems.Contains(ingredients);
    }

    public ItemBundle Craft(ItemSet availableItems)
    {
        availableItems.Remove(ingredients);
        return output.Clone();
    }

    public override string ToString()
    {
        StringBuilder sb = new StringBuilder();
        sb.Append(output);
        sb.Append(" = ");
        sb.Append(ingredients);
        return sb.ToString();
    }
}

public class ItemBundle
{
    public readonly string Name;
    public int Amount { get; private set; }
    public bool IsEmpty { get { return Amount == 0; } }

    public ItemBundle(string name, int amount = 1)
    {
        ThrowNegativeAmounts(amount);
        Name = name;
        Amount = amount;
    }

    private static void ThrowNegativeAmounts(int amount)
    {
        if (amount < 0)
            throw new ArgumentException("ItemStack does not support negative amount", "amount");
    }

    public void Change(int amount)
    {
        int result = Amount + amount;
        ThrowNegativeAmounts(result);
        Amount = result;
    }

    public void Set(int amount)
    {
        ThrowNegativeAmounts(amount);
        Amount = amount;
    }

    public override string ToString()
    {
        return string.Format("{0} {1}", Amount, Name);
    }

    public ItemBundle Clone()
    {
        return new ItemBundle(Name, Amount);
    }
}

Log output

Craftable Recepies
==================
  1 flag = 1 cloth, 1 stick
  1 hammer = 1 stone, 1 stick

Inventory Contents
==================
  2 cloth
  2 stick
  2 stone

Crafting hammer

Inventory Contents
==================
  2 cloth
  1 stick
  1 stone
  1 hammer

Craftable Recepies
==================
  1 flag = 1 cloth, 1 stick
  1 hammer = 1 stone, 1 stick
  1 heavy hammer = 1 stone, 1 hammer

Crafting heavy hammer

Inventory Contents
==================
  2 cloth
  1 stick
  1 heavy hammer

Craftable Recepies
==================
  1 flag = 1 cloth, 1 stick