Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

[SOLVED] How to make one object type for different subclasses with scriptable objects?

Discussion in 'Scripting' started by zurisar, Feb 5, 2020.

  1. zurisar

    zurisar

    Joined:
    Nov 4, 2019
    Posts:
    24
    Hi guys, I still not found answer on my question, I still not found way how to make it clear and easy.

    I have some scriptable objects, for example: wood, iron, shovel.

    Basic item class
    Code (CSharp):
    1. public class Item : ScriptableObject
    2. {
    3.     public int itemID;
    4.     public enum itemTypes
    5.     {
    6.         Resource,
    7.         Product,
    8.         Machine
    9.     }
    10.     public itemTypes itemType;
    11.     public string itemTitle;
    12.     public Sprite itemIcon;
    13.     public int itemValue;
    14.     public double weight = 0;
    15.  
    16.     public virtual void Use()
    17.     {
    18.         Debug.Log("You touch " + itemTitle);
    19.     }
    20.  
    21. }
    Product (shovel) item class
    Code (CSharp):
    1. public class Production : Item
    2. {
    3.     public double priceSell;
    4.     public List<Item> prodList = new List<Item>();
    5.  
    6.     public Inventory inventory;
    7.  
    8.     private void Awake()
    9.     {
    10.         GameObject gameObject = GameObject.Find("Inventory");
    11.         inventory = gameObject.GetComponent<Inventory>();
    12.     }
    13.  
    14.     public override void Use()
    15.     {
    16.         base.Use();
    17.  
    18.         Debug.Log("ItemID: " + itemID);
    19.        Item item = inventory.GetItemByID(itemID);
    20.        if (inventory.CheckItem(item))
    21.         {
    22.             Debug.Log("You have this item " + itemTitle);
    23.         }
    24.        else
    25.         {
    26.             Debug.Log("You haven't " + itemTitle);
    27.         }
    28.     }
    29. }
    I can't check item in iventory, because inventory work with base Item class, it want Item type object for do functions.
    For example part of Inventory.cs
    Code (CSharp):
    1. public Item GetItemByID(int itemID)
    2.     {
    3.         Debug.Log("GetItemByID called, itemID: " + itemID);
    4.         return factoryItems.Find(x => x.itemID == itemID);
    5.     }
    6.  
    7.     public bool CheckItem(Item item)
    8.     {
    9.         Debug.Log("Check item function called!");
    10.         return factoryItems.Exists(x => x == item);
    11.     }
    Inventory hold all items with Item object type and use List<Item> for that. But my items doesn't have Item object type, they are Resource(wood, iron) and Product(shovel) item types. I know two ways how to solve it, but I don't like anyone.
    1. Remove item subclasses and make all items like Item, use enum to get itemType and then use switch for each type when I want to Use() it - I don't like this, because a lot of null variables will be for each item, for example I use List prodList for item recipe/blueprint(only Product items need this), simple wood doesn't need this variable at all.
    2. Use it at is, but also with enum and check itemType with if and switch. - I lot of hard and massive logic in code, easy way to make problems for self.

    Give me advice please how I can work with any item type in my inventory?
     
  2. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,520
    I'm not 100% sure what you want, it seems you forgot to ask your question, but does this help?
    Code (csharp):
    1.  
    2. public abstract class Item : ScriptableObject
    3. {
    4.     public abstract bool IsProduct { get; }
    5. }
    6.  
    7. public class ItemProduct : Item
    8. {
    9.     public override bool IsProduct { get { return true; } }
    10. }
    11.  
    12. public class ItemResource : Item
    13. {
    14.     public override bool IsProduct { get { return false; } }
    15. }
    16.  
    17. Item item = ...;
    18. if (item.IsProduct) item.Use();
    19.  
     
  3. zurisar

    zurisar

    Joined:
    Nov 4, 2019
    Posts:
    24
    Thanks, but it doesn't :)

    Problem in object's types, Inventory script hold all items as Item, but actually some of them are Resource and some are Product.

    So when script ask Invetory to CheckItem(item), I got NullPointReference, because as I think, script can't use Resource/Product type, it know only Item type.

    So main question is, how I can attach parent class type for child class objects? If all this object are scriptable and has one parent class.
     
  4. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,520
    So you are trying to cast the items to the Resource/Product type, but sometimes that results in a null pointer, since the Item in question is not of that type. Then I see a few options:
    - Do a check first:
    Code (csharp):
    1. if (!(item is Product)) return;
    - Add the check in the root class, as I described above:
    Code (csharp):
    1. if (!item.IsProduct) return;
    - Do a null check after the cast:
    Code (csharp):
    1. Product product = (Product)item;
    2. if (product == null) return;
    If you want to make things a little more flexible, you could do essentially the same, but use interfaces for the check:
    Code (csharp):
    1. public class Product : Item, IProduct
    2. if (!(item is IProduct)) return;
    3.  
    That way you can have different subclasses implement the same interface and still only require one check. Also, a single subclass could implement multiple interfaces.

    In your case I can imagine this to be an interface for example:
    Code (csharp):
    1.  
    2. public interface IPrice
    3. {
    4.     public double PriceSell { get; }
    5. }
    6.  
    On a side note, don't store prices as floating point values, use an integer with fixed point representation instead. (Store as int in cents, half the memory, none of the rounding problems.)
     
    Last edited: Feb 5, 2020
  5. zurisar

    zurisar

    Joined:
    Nov 4, 2019
    Posts:
    24
    I got it work, but I don't understand logic actually.

    My inventory script is instance
    Code (CSharp):
    1. public static Inventory instance;
    2.  
    3.     private void Start()
    4.     {
    5.         instance = this;
    6.         UpdatePanelSlots();
    7.     }
    First mistake was get Inventory component
    Code (CSharp):
    1. private void Awake()
    2.     {
    3.         GameObject gameObject = GameObject.Find("Inventory");
    4.         inventory = gameObject.GetComponent<Inventory>();
    5.     }
    In subclass scripts, that code is useless.

    Al I need is call
    Inventory.instance.CheckItemByID(itemID) and it works. Maybe I don't understand object types for scripable objects...

    For now I use:
    Code (CSharp):
    1. public override void Use()
    2.     {
    3.         base.Use();
    4.  
    5.         Debug.Log("Try to get item " + itemID);
    6.         Item item = Inventory.instance.GetItemByID(itemID);
    7.         if(item)
    8.         {
    9.             Inventory.instance.AddItem(item);
    10.         }
    11.  
    12.     }
    I still have problems, if item not in inventory, script can't found it. But this is just question of time. I need to improve it with some ItemsManager or something...

    Thanks for your help and sorry for my stupidity ;)