Search Unity

Help with simple Crafting.

Discussion in 'Scripting' started by rogueghost, Jan 6, 2019.

  1. rogueghost

    rogueghost

    Joined:
    Jun 2, 2018
    Posts:
    76
    Hello. :)

    I'm trying to make a simple Crafting logic for a game. It basically consists of a class of Items and
    Recipes, the Items class have some settings such as Name, ID and Amount and i'm using Lists to Add/Remove the Items from the game.
    What i'm trying to make is to take a set of Items from it's Item List and combine them to make
    another Item in a Craft function using the set of Items IDs (Or maybe something fancier?), and also
    check if the Items amount the Recipe requires are available. all of this done in the same script.

    This is what i have so far:

    Code (CSharp):
    1. [System.Serializable]
    2.     public class Item
    3.     {
    4.         public string itemName;
    5.         public int itemID; //This is used to keep track of this item and maybe for crafting it?
    6.         public int itemAmount; //This is to know how many of this Item are in the game.
    7.         public int itemLimit; //This is the limit amount of this Item.
    8.         public GameObject itemObject;
    9.     }
    10.  
    11.     [System.Serializable]
    12.     public class CraftingRecipe
    13.     {
    14.         public string recipeName;
    15.         public int recipeID;
    16.         public int recipeAmount; //The amount of the Item to output.
    17.         public GameObject recipeObject; //The output Object to be used in the scene.
    18.     }
    19.  
    20.     public List<Item> items = new List<Item>();
    21.     public List<CraftingRecipe> recipes = new List<CraftingRecipe>();
    22.  
    23.     public void Craft(int id)
    24.     {
    25.        //Craft the item.
    26.     }
    I know it sounds like a simple thing to do, but i've spent a few days trying to figure this out.
    Please use the settings i've shown or maybe improve it if you wish.
    I'm really looking forward to a answer to this. ;)
     
  2. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    Hi @rogueghost

    I haven't ever written proper crafting system, but I'd write pseudo code myself first. It would be helpful.

    List the steps and put them in correct order.

    You already have item and recipe classes, you don't need much more, your Craft is now basically one empty method.

    First it is helpful to check if you have each ingredient / component you need.

    If you have each component and correct amount, then you can craft the item, which basically means reducing each component amount needed for crafting from your inventory.

    Then spawn craftable item result object.
     
    rogueghost likes this.
  3. rogueghost

    rogueghost

    Joined:
    Jun 2, 2018
    Posts:
    76
    @eses Thanks for the reply! :)

    This is one thing i'm having trouble with and need a bit of help with the code.
    How do i reference the Item Ingredients in the Recipe and check for it in the Craft() function? So that i can check if its Amount is enough to craft and then decrease it and other stuff needed?
     
    Last edited: Jan 6, 2019
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    So here is our crafting system in action (mind the bad colour, to reduce size this is a highly compressed gif):
    CraftingExample.gif

    How we accomplish this is that we have ScriptableObject's for both item's and recipes:

    InventoryItem:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4.  
    5. using com.spacepuppy;
    6. using com.spacepuppy.Scenario;
    7. using com.spacepuppy.Utils;
    8.  
    9. using com.mansion.Entities.Inventory.UI;
    10.  
    11. namespace com.mansion.Entities.Inventory
    12. {
    13.  
    14.     [CreateAssetMenu(fileName = "InventoryItem", menuName = "Inventory/InventoryItem")]
    15.     public class InventoryItem : ScriptableObject, INameable, IObservableTrigger
    16.     {
    17.  
    18.         #region Fields
    19.  
    20.         [SerializeField]
    21.         private InventoryUsage _usage;
    22.  
    23.         [SerializeField]
    24.         private Sprite _icon;
    25.  
    26.         [SerializeField]
    27.         private InventoryItemViewUI _menuVisual;
    28.    
    29.         [SerializeField]
    30.         private string _title;
    31.  
    32.         [SerializeField]
    33.         private string _description;
    34.  
    35.         [SerializeField]
    36.         [Tooltip("Items that are unique can only be added to the InventoryPouch once. Re-adding it doesn't do anything.")]
    37.         private bool _unique = true;
    38.  
    39.         [SerializeField]
    40.         private bool _consumeOnUse = true;
    41.  
    42.         [SerializeField]
    43.         private Trigger _onUsed;
    44.    
    45.         #endregion
    46.  
    47.         #region CONSTRUCTOR
    48.    
    49.         public InventoryItem()
    50.         {
    51.             _nameCache = new NameCache.UnityObjectNameCache(this);
    52.         }
    53.  
    54.         #endregion
    55.  
    56.         #region Properties
    57.  
    58.         public virtual InventoryUsage Usage
    59.         {
    60.             get { return _usage; }
    61.             set { _usage = value; }
    62.         }
    63.    
    64.         public Sprite Icon
    65.         {
    66.             get { return _icon; }
    67.             set { _icon = value; }
    68.         }
    69.    
    70.         public virtual InventoryItemViewUI MenuVisual
    71.         {
    72.             get { return _menuVisual; }
    73.             set { _menuVisual = value; }
    74.         }
    75.  
    76.         public string Title
    77.         {
    78.             get { return _title; }
    79.             set { _title = value; }
    80.         }
    81.  
    82.         public string Description
    83.         {
    84.             get { return _description; }
    85.             set { _description = value; }
    86.         }
    87.  
    88.         public bool Unique
    89.         {
    90.             get { return _unique; }
    91.             set { _unique = value; }
    92.         }
    93.  
    94.         public bool ConsumeOnUse
    95.         {
    96.             get { return _consumeOnUse; }
    97.             set { _consumeOnUse = value; }
    98.         }
    99.  
    100.         public Trigger OnUsed
    101.         {
    102.             get { return _onUsed; }
    103.         }
    104.  
    105.         #endregion
    106.  
    107.         #region Methods
    108.  
    109.         public void Use()
    110.         {
    111.             _onUsed.ActivateTrigger(this, null);
    112.         }
    113.  
    114.         public virtual void OnItemAddedToInventory(InventoryPouch pouch)
    115.         {
    116.             //do nothing
    117.         }
    118.  
    119.         public virtual void OnItemRemovedFromInventory(InventoryPouch pouch)
    120.         {
    121.             //do nothing
    122.         }
    123.    
    124.         public virtual bool PlayerHasInInventory(PlayerEntity player, bool isEquipped)
    125.         {
    126.             if (isEquipped) return false;
    127.  
    128.             var inventory = player.GetComponent<InventoryPouch>();
    129.             if (inventory == null) return false;
    130.  
    131.             return inventory.Items.Contains(this);
    132.         }
    133.  
    134.         #endregion
    135.  
    136.         #region INameable Interface
    137.  
    138.         private NameCache.UnityObjectNameCache _nameCache;
    139.         public new string name
    140.         {
    141.             get { return _nameCache.Name; }
    142.             set { _nameCache.Name = value; }
    143.         }
    144.         string INameable.Name
    145.         {
    146.             get { return _nameCache.Name; }
    147.             set { _nameCache.Name = value; }
    148.         }
    149.         public bool CompareName(string nm)
    150.         {
    151.             return _nameCache.CompareName(nm);
    152.         }
    153.         void INameable.SetDirty()
    154.         {
    155.             _nameCache.SetDirty();
    156.         }
    157.  
    158.         #endregion
    159.  
    160.         #region IObservableTrigger Interface
    161.  
    162.         Trigger[] IObservableTrigger.GetTriggers()
    163.         {
    164.             return new Trigger[] { _onUsed };
    165.         }
    166.  
    167.         #endregion
    168.  
    169.     }
    170.  
    171. }
    172.  
    ItemRecipe:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5.  
    6. using com.spacepuppy;
    7. using com.spacepuppy.Scenario;
    8. using com.spacepuppy.Utils;
    9.  
    10. namespace com.mansion.Entities.Inventory
    11. {
    12.  
    13.     [CreateAssetMenu(fileName = "ItemRecipe", menuName = "Inventory/ItemRecipe")]
    14.     public class ItemRecipe : ScriptableObject, INameable
    15.     {
    16.    
    17.         #region Fields
    18.  
    19.         [SerializeField]
    20.         private InventoryItem _item;
    21.  
    22.         [SerializeField]
    23.         [ReorderableArray()]
    24.         [DisableOnPlay()]
    25.         private InventoryItem[] _recipe;
    26.  
    27.         [SerializeField]
    28.         private bool _consumeItems = true;
    29.  
    30.         [SerializeField]
    31.         private Trigger _onCreated;
    32.  
    33.         #endregion
    34.  
    35.         #region Properties
    36.  
    37.         public InventoryItem Item
    38.         {
    39.             get { return _item; }
    40.             set
    41.             {
    42.                 _item = value;
    43.             }
    44.         }
    45.  
    46.         public InventoryItem[] Recipe
    47.         {
    48.             get { return _recipe; }
    49.             set { _recipe = value; }
    50.         }
    51.  
    52.         public bool ConsumeItems
    53.         {
    54.             get { return _consumeItems; }
    55.             set { _consumeItems = value; }
    56.         }
    57.  
    58.         public int RecipeItemCount
    59.         {
    60.             get { return _recipe != null ? _recipe.Length : 0; }
    61.         }
    62.  
    63.         public Trigger OnCreated
    64.         {
    65.             get { return _onCreated; }
    66.         }
    67.  
    68.         #endregion
    69.  
    70.         #region Methods
    71.  
    72.         /// <summary>
    73.         /// Tests if the recipe uses the item at all.
    74.         /// </summary>
    75.         /// <param name="item"></param>
    76.         /// <returns></returns>
    77.         public bool UsesItem(InventoryItem item)
    78.         {
    79.             if (_recipe == null || _recipe.Length == 0) return false;
    80.  
    81.             return System.Array.IndexOf(_recipe, item) >= 0;
    82.         }
    83.  
    84.         /// <summary>
    85.         /// Tests if this combination works for this recipe.
    86.         /// </summary>
    87.         /// <param name="a"></param>
    88.         /// <param name="b"></param>
    89.         /// <returns></returns>
    90.         public bool Matches(InventoryItem a, InventoryItem b)
    91.         {
    92.             if (_recipe == null || _recipe.Length != 2) return false;
    93.  
    94.             if (_recipe[0] == a)
    95.                 return _recipe[1] == b;
    96.             else if (_recipe[1] == a)
    97.                 return _recipe[0] == b;
    98.             else
    99.                 return false;
    100.         }
    101.  
    102.         /// <summary>
    103.         /// Tests if this combination works for this recipe.
    104.         /// </summary>
    105.         /// <param name="arr"></param>
    106.         /// <returns></returns>
    107.         public bool Matches(params InventoryItem[] arr)
    108.         {
    109.             if (_recipe == null) return arr == null;
    110.             if (arr == null) return _recipe == null;
    111.             if (_recipe.Length != arr.Length) return false;
    112.  
    113.             return !_recipe.Except(arr).Any();
    114.         }
    115.  
    116.         public bool Matches(ICollection<InventoryItem> coll)
    117.         {
    118.             if (_recipe == null) return coll == null;
    119.             if (coll == null) return _recipe == null;
    120.             if (_recipe.Length != coll.Count) return false;
    121.  
    122.             return !_recipe.Except(coll).Any();
    123.         }
    124.  
    125.         public bool PartialMatch(params InventoryItem[] arr)
    126.         {
    127.             if (_recipe == null) return arr == null;
    128.             if (arr == null) return _recipe == null;
    129.  
    130.             return !arr.Except(_recipe).Any();
    131.         }
    132.  
    133.         public bool PartialMatch(ICollection<InventoryItem> coll)
    134.         {
    135.             if (_recipe == null) return coll == null;
    136.             if (coll == null) return _recipe == null;
    137.  
    138.             return !coll.Except(_recipe).Any();
    139.         }
    140.  
    141.         #endregion
    142.  
    143.         #region INameable Interface
    144.  
    145.         private NameCache.UnityObjectNameCache _nameCache;
    146.         public new string name
    147.         {
    148.             get { return _nameCache.Name; }
    149.             set { _nameCache.Name = value; }
    150.         }
    151.         string INameable.Name
    152.         {
    153.             get { return _nameCache.Name; }
    154.             set { _nameCache.Name = value; }
    155.         }
    156.         public bool CompareName(string nm)
    157.         {
    158.             return _nameCache.CompareName(nm);
    159.         }
    160.         void INameable.SetDirty()
    161.         {
    162.             _nameCache.SetDirty();
    163.         }
    164.  
    165.         #endregion
    166.  
    167.     }
    168.  
    169. }
    170.  
    (you may notice more than 1 version of Matches... this is because early versions of our crafting system only allowed for 2 items to combine... we later added the ability to do multi-item combination like this fishing rod)
    recipe.png

    As for the general logic for combining here is the inventory screens update cycle. Mind you that because it supports multiple interactions (using, combining, equipping, etc), there is more going on here than JUST combining.

    Code (csharp):
    1.  
    2.         private void Update()
    3.         {
    4.             //boiler plate
    5.             _display.Spindle.DefaultUpdate_AnimateSpindleElements(true);
    6.             if (!_display.IsShowing || _routine != null) return;
    7.  
    8.             var input = Services.Get<IInputManager>().GetDevice<MansionInputDevice>(Game.MAIN_INPUT);
    9.             if (input == null || !_display.IsShowing) return;
    10.  
    11.             if (input.GetExitMenu(true, true))
    12.             {
    13.                 _display.Hide();
    14.                 return;
    15.             }
    16.  
    17.             if (_display.Spindle.DefaultUpdate_ProcessSpindleInputRotation(input)) this.UpdateDisplayText();
    18.        
    19.             //process for this state
    20.             switch(_state)
    21.             {
    22.                 case State.Standard:
    23.                     {
    24.                         if (input.GetButtonState(MansionInputs.Action) == ButtonState.Down)
    25.                         {
    26.                             var item = _display.Spindle.GetCurrentlyFocusedInventoryView();
    27.                             switch (item != null && item.Item != null ? item.Item.Usage : InventoryUsage.Default)
    28.                             {
    29.                                 case InventoryUsage.Default:
    30.                                     //do nothing
    31.                                     break;
    32.                                 case InventoryUsage.Equippable:
    33.                                     if (item.Item is WeaponInventoryItem)
    34.                                     {
    35.                                         //we close on select
    36.                                         if (_display.CurrentlyEquippedWeaponIndex == _display.Spindle.FocusedIndex ||
    37.                                             _display.ChangeEquippedWeapon(_display.Spindle.FocusedIndex))
    38.                                             this.Invoke(() => _display.Hide(), 0.1f, SPTime.Real);
    39.                                     }
    40.                                     break;
    41.                                 case InventoryUsage.Usable:
    42.                                     {
    43.                                         _routine = this.StartRadicalCoroutine(this.DoConsumeItem(item));
    44.                                     }
    45.                                     break;
    46.                                 case InventoryUsage.Combinable:
    47.                                     {
    48.                                         _state = State.WaitingOnCombination;
    49.                                         _waitingOnCombinationItems.Add(item.Item);
    50.                                         item.SetHighlighted(true, cakeslice.OutlineEffect.OutlinePreset.B);
    51.                                         for(int i = 0; i < _display.Items.Count; i++)
    52.                                         {
    53.                                             if (_display.Items[i] != item)
    54.                                                 _display.Items[i].SetHighlighted(false);
    55.                                         }
    56.                                     }
    57.                                     break;
    58.                             }
    59.                         }
    60.                     }
    61.                     break;
    62.                 case State.WaitingOnCombination:
    63.                     {
    64.                         if (input.GetButtonState(MansionInputs.Action) == ButtonState.Down)
    65.                         {
    66.                             var item = _display.Spindle.GetCurrentlyFocusedInventoryView();
    67.                             if(item != null && item.Item != null)
    68.                             {
    69.                                 if (Game.EpisodeSettings.RecipeSet == null)
    70.                                 {
    71.                                     _state = State.Standard;
    72.                                     _display.SetHighlights();
    73.                                     _waitingOnCombinationItems.Clear();
    74.                                     _onCombinationFail.ActivateTrigger(this, null);
    75.                                 }
    76.                                 else
    77.                                 {
    78.                                     item.SetHighlighted(true, cakeslice.OutlineEffect.OutlinePreset.B);
    79.                                     _waitingOnCombinationItems.Add(item.Item);
    80.  
    81.                                     int foundMatch = 0; //0 = no, 1 = partial, 2 = yes
    82.                                     var recipes = Game.EpisodeSettings.RecipeSet;
    83.                                     foreach (var recipe in recipes.GetAllAssets<ItemRecipe>())
    84.                                     {
    85.                                         if (recipe.Matches(_waitingOnCombinationItems))
    86.                                         {
    87.                                             _routine = this.StartRadicalCoroutine(this.DoCombineItem(recipe));
    88.                                             foundMatch = 2;
    89.                                             break;
    90.                                         }
    91.                                         else if(recipe.PartialMatch(_waitingOnCombinationItems))
    92.                                         {
    93.                                             foundMatch = 1;
    94.                                         }
    95.                                     }
    96.  
    97.                                     if (foundMatch == 0)
    98.                                     {
    99.                                         _state = State.Standard;
    100.                                         _display.SetHighlights();
    101.                                         _waitingOnCombinationItems.Clear();
    102.                                         _onCombinationFail.ActivateTrigger(this, null);
    103.                                     }
    104.                                 }
    105.                             }
    106.                         }
    107.                     }
    108.                     break;
    109.             }
    110.        
    111.         }
    112.  
    113.         private System.Collections.IEnumerator DoCombineItem(ItemRecipe recipe)
    114.         {
    115.             if (_display.ConfirmPrompt == null) goto Confirmed;
    116.  
    117.             var result = _display.ConfirmPrompt.Show();
    118.             yield return result;
    119.             yield return null; //wait one extra frame for effect
    120.  
    121.             if (result != null && result.SelectedConfirm)
    122.                 goto Confirmed;
    123.             else
    124.             {
    125.                 _onCombinationFail.ActivateTrigger(this, null);
    126.                 goto Cleanup;
    127.             }
    128.  
    129.             Confirmed:
    130.             if(recipe.ConsumeItems)
    131.             {
    132.                 foreach (var item in _waitingOnCombinationItems)
    133.                 {
    134.                     _display.InventoryPouch.Items.Remove(item);
    135.                 }
    136.             }
    137.             _waitingOnCombinationItems.Clear();
    138.  
    139.             if (recipe.Item != null) _display.InventoryPouch.Items.Add(recipe.Item);
    140.             recipe.OnCreated.ActivateTrigger(recipe, null);
    141.             _onCombinationSuccess.ActivateTrigger(this, null);
    142.  
    143.             yield return WaitForDuration.Seconds(0.1f, SPTime.Real);
    144.             //_display.Hide();
    145.  
    146.             if(_display.IsShowing)
    147.             {
    148.                 _display.SyncItemCollections();
    149.                 int newIndex = _display.GetItemIndex(recipe.Item);
    150.                 if (newIndex >= 0) _display.Spindle.SetFocusedIndex(newIndex, true);
    151.                 _display.UpdateCurrentlyEquippedHighlight(); //in-case what was combined was an euipped item
    152.                 this.UpdateDisplayText();
    153.             }
    154.  
    155.             Cleanup:
    156.             _state = State.Standard;
    157.             if (_display.IsShowing) _display.SetHighlights();
    158.             _waitingOnCombinationItems.Clear();
    159.             _routine = null;
    160.         }
    161.  
    Basically I have a state enum that tracks what state the inventory screen is in.

    When someone selects the first item I determine what sort of item it is... if it's a combinable item I go into a 'WaitingOnCombination' state. Then as more items are selected I test if any of the recipes on hand match the combination I currently have via the 'matches' method on the recipes. I keep doing this until they either cancel, or a recipe matches. At which point I remove the old items and add the new one.


    ...

    Now this may not be exactly what you need... it is designed specifically for our game which has only very basic crafting. We don't have things like using y items of x, or minecraft style crafting table/pattern stuff.

    It's a really simple "these items combine to make that item" system. Since our game is more a survival horror meets old SCUMM/Adventure games (like secret of monkey island).
     
    Last edited: Jan 7, 2019
    rogueghost likes this.
  5. rogueghost

    rogueghost

    Joined:
    Jun 2, 2018
    Posts:
    76
    @lordofduct This is some cool stuff, i don't understand much of what is happening in the code but seems like something i could use. Thanks for sharing!
     
    Last edited: Jan 7, 2019
  6. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    How do you represent the crafted item if only 2 of 3 items are added? Will the resulting Item reflect this? Didnt see this in the ItemRecipe code, but I might missed something. Good approach, would do something similar myself.

    edit: not often you see people use goto in C# :D Though I have been thinking of using them myself for coroutines, but haven't needed it yet.
     
  7. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    I assume you mean if like the recipe can be AB, or BC of ABC.

    We currently don't have a representation for that, as we don't need it. If there is a corner case that we do we'd just create 2 recipes for each AB and BC that result in the same item.

    I found them useful in coroutine logic flow. Since a coroutine usually represents a linear flow of time, getting to jump forward rather than into if statements reads more like the linear flow of time.

    I also like C#'s goto despite people hating on goto as a whole. Jumps are common in lower level languages and as long as you know how you're using it they're not as evil as people make them out to be. It's only when you're in a language that lets you jump ANYWHERE in code and people just willy nilly jump around exploiting that, and trouble starts.

    Thankfully, C# actually is pretty protective about 'goto'. You can't goto out of your current context. I can't jump into another method or anything like that. The compiler puts safe limitations on it.

    Of course, you still want to keep a goto readable.
     
  8. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,983
    Im confused as to what you gain over just having a "Cleanup()" and "Confirmed()" method that would adhere to c# standards? GOTO is really not a good mechanism and is widely avoided by most programmers, I really am struggling to get what you are gaining that you cannot do without GOTO?
     
  9. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    I beg to differ about it not being a good mechanism.

    A goto is a jump/branch. This mechanism is extremely common in code. Calling a function, yeah, that's a branch/jump. If statement? Yeah, that's a jump/branch. It's just got different syntax in your high-level language, but at the end of the day... they're jump statements!

    It not being a good mechanism is a rule of thumb that comes down from abuse of GOTO. Any mechanism can be abused. Over the many years of people abusing things the internet by and large just dislikes goto... it's not because of goto in and of itself, but because of the bad things people have seen done in it.

    It's very much related to BASIC and VB and the sort. The rise of hatred for goto corresponds with the rise of hatred for those languages. Namely because BASIC/VB use GOTO a lot in its code, it's how you get a lot of things done in those languages.

    Problem is those languages were overrun by novice/amateur programmers who didn't know what they were doing.

    Thus the code smell that arose out of it.

    Thing is, you can abuse most mechanisms. I've seen OOP principles like inheritance/composition used poorly. I've seen design patterns like Singleton (oh that one has some real hate on the internet yet you find it in Unity a lot) get trashed. Bad programmers will do bad things with otherwise reasonable mechanisms.

    Goto hatred is dogma rather than logic.

    ...

    Now, you don't have to use goto.

    I do.

    In very limitted settings.

    What I gain from not just calling a function? Well... the overhead of that function for one. The disconnect from the linear logic of my coroutine for another. I described exactly why I use it in a coroutine... a coroutine is intended to represent a linear passage of time, and a goto allows me to write code that reads in a linear fashion like that.

    I mean if we want to argue code smell here... the entire existence of a Coroutine is a massive code smell. Abusing the 'iterator function' pattern from C# to coerce it into this "coroutine" is heavily frowned upon outside of Unity.

    But we do it, cause it gets the job done.

    ...

    I have 12 years professional experience programming, 20+ years hobbyist experience. I don't care what you tell me is right/wrong in the programming world. I've seen a lot of rights/wrongs come and go over the years. It's all hub-bub and blustering.

    If you know what it's doing, and you do it correclty/safely... it's not a problem in my book.
     
    Last edited: Jan 7, 2019
    Munchy2007 likes this.
  10. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,983
    Actually you are talking to an ex VB programmer. Also I started game development using darkBASIC which was written in BASIC so I am not unfamiliar with GOTO and have used it tons in the past, but I still wouldnt use it today in C# as I like my code to be maintainable by anyone, even if I died etc.

    Anyway, I did not mean any offence, I was just interested in what you gain out of it and now you have answered. Certainly you dont need to explain how long you have programmed and blah blah blah, if your able to justify it your able to justify it and that would have been enough :D

    And yes, you are right, I would also argue a coroutine is a bad mecanism too, as I would use async this day and age seeing as unity supports c# 4.0 onwards anyway now. Again, just interested to see what you get out of it. Was not saying you have to program my way!
     
  11. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    My listing how long I've developed wasn't a point to boast my experience. It was to point out that I've seen over the years the comings and goings of various bias. It was an added justification against the dogma against goto.

    I already justified my use before you first responded. You just didn't like my justification, and therefore asked me to justify it again. So I added more, since the one I originally gave clearly wasn't enough for you.

    I already said what I got out of it. Readable code.

    If people can't read goto's, that's on them. They're fairly straight forward.

    It's not like my goto's are wild spaghetti messes. They're effectively the equivalent of a try/catch/finally ensure finally gets performed. (noting that try/catch/finally can not be performed in a coroutine since iterator functions don't allow try/catch).

    It too like readable/maintainable code. And I fail to see how my usage of goto lacks maintainability.
     
    Last edited: Jan 7, 2019
  12. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,983
    Okay, cool.

    I asked a question and you replied, no big deal. Thanks for taking time to answer, but Im going to leave this now as your clearly taking this a bit personally, which was not my intention at all!
     
  13. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    AndersMalmgren asked me, I explained, and then you asked me again is to what I'm referring. Meaning you either didn't read what I had already said, or just didn't consider it a valid justification.

    This is why I said:
    Blowing this out of proportion?

    I'm just responding.
     
  14. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,983
    Fair enough, your answers read a little bit like arguing rather than answering, but maybe thats me.

    Anyway, again as said before, thanks for answering. Nice crafting system and nice looking game. Good luck to you.

    EDIT: OP you can have a look at this for a simple solution if your not too experienced with C# and Unity:


    Otherwise @lordofduct code will provide you with a more unique and probably more versatile crafting system. If I was going to choose one I would use what has been posted in here, but you mentioned you had trouble reading the code so I thought you might like a backup easier option :)
     
    Last edited: Jan 7, 2019
    rogueghost likes this.
  15. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    I can see a place for goto in coroutines. Though it could also be a sign your coroutine needs redesign.

    Anyway Coroutines are mini workflows, and some workflows can be hard defining only with linear while loops and if statements. Sometimes you might need to jump from A to B. Though like I said, haven't needed it yet.
     
  16. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    Well yes, It's an argument... not in the "yell and throw stuff" sense. But rather a debate argument.

    You don't agree with my usage, I do. You requested for me to explain (justify) my usage instead of using other C# mechanisms like a Cleanup method.

    Thusly a debate ensued.

    You may not have intended a debate. But it confused me that you'd ask me to answer a question that I already answered. That signaled as a 'debate'. As if you had arguments for why you felt your stance was correct and wanted me to further explain mine.
     
    MadeFromPolygons likes this.
  17. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    I agree with this.

    I've only really ever needed it in minor cases. Like the one here. I wanted to keep my cleanup code at the end, and so I put it at the end. Instead of repeating it, or breaking it out into another function. A function that would only ever get called by this coroutine since it's pertinent to only this coroutine.

    Noting that the class has several other things going on. Of course that says I could probably factor this logic out of that class and into its own behaviour of some sort. But... I had a game to write and launch for halloween. A goto got the job done in the fewest lines of code and still was readable.

    Hell the 'confirmed' portion of the code could be done if I just did a negative check in the if statement. That probably snuck in from code revisions. I bet that the first if statement at one point did something, but then it got removed, and just wasn't refactored out.
     
  18. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,983
    Fair enough, probably the fact that everything is being read over text. Its hard to guage someones "tone" from text, and what you write as "not yelling and throwing stuff" I may read as "yelling and throwing stuff" haha :D

    Anyway no harm done, everyones question was answered and OP has been given some decent resources.
     
  19. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    The built in asynchronous tasks in C sharp doesn't automatically make Coroutines a anti pattern. They aim to solve the same thing, execute asynchronous code in a synchronous manner.

    Though the asynchronous framework is a bit neater becasue of the native compiler support, plus Task of T is really nice and not very easy to solve with Coroutines, you need to create custom yield instructions etc
     
  20. ClaudiaTheDev

    ClaudiaTheDev

    Joined:
    Jan 28, 2018
    Posts:
    331
    I also needed a crafting system and dowloaded this free asset "Inventory Master":
    https://assetstore.unity.com/packages/tools/gui/inventory-master-ugui-26310

    It has a item "database" to which you can add items.
    It also has a recipe/blueprint "database" where you can create crafting recipes with the items.

    Perhaps you can even take it like it is - when not you can learn a lot from the code.
    I have my own system now but "Inventory Master" was a great help.
     
  21. Munchy2007

    Munchy2007

    Joined:
    Jun 16, 2013
    Posts:
    1,735
    Good lord! And here was me thinking I was the only one left that still used it (occasionally) :)