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

Resolved Inheritance and an Item Class

Discussion in 'Scripting' started by PaperMouseGames, Feb 16, 2022.

  1. PaperMouseGames

    PaperMouseGames

    Joined:
    Jul 31, 2018
    Posts:
    434
    So I'm thinking of restructuring my the Item class in my game.

    Right now I have an
    Item
    class and an
    ItemSO
    class (which is almost identical but is a scriptable object so I can create premade items and stick them into inventories). Each one also has an
    ItemType
    which is an empty scriptable object being used as an identifier how I distinguish between things like potions, weapons, etc.

    This works really well and on the surface it is exactly what I need but as my
    Item
    class has grown I've found that it has gotten really bloated.

    So now my
    Item
    class has a ton of properties and each individual item only really uses a handful of them. Weapons don't use the
    healAmount
    property meat for potions, and potions don't use the
    damage
    property, etc.

    So I was wondering if I should maybe consider a different approach.

    I though, what if I made the
    Item
    class a parent class, and then made things like a
    Weapon
    class and a
    Potion
    class that inherit from Item.

    That way when I put stuff into an inventory I just check if it's an Item, and if I have an inventory that only holds certain types of items it can check if it's a potion or whatever.

    So on the surface the functionality would be the same, but it might be less bloated since one class wouldn't need to hold every single property for every type of item.

    Any thoughts on this? I always hesitate to use inheritance because I do feel like it can be a more rigid way to structure things, but I thought I'd see if anyone had a better idea to help organize an
    Item
    class
     
  2. _met44

    _met44

    Joined:
    Jun 1, 2013
    Posts:
    633
    Maybe have a PropertySO and in your items a list of PropertySO ?

    and this way you can invent new property without having to modify existing code
     
    eses likes this.
  3. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    I too have been having problems with similar setups.

    Don't have much to say other than having subclasses of Item class won't work too well with Inspector / Lists. Unity serializes those based on base class type, unless you use relatively new feature SerializeReference, but then you'll need to create some sort of custom editor or context menu options (for example) so you can add those classes into your list. Of course you don't necessarily need to use Inspector at all if you are loading your items from files.

    Edit. If you are using Scriptable Objects (after reading your post again seems like you are), you could simply use subclasses of Item without such issues, using SerializeReference is for serializable plain C# classes.
     
    Last edited: Feb 16, 2022
  4. PaperMouseGames

    PaperMouseGames

    Joined:
    Jul 31, 2018
    Posts:
    434
    Thanks for the reply! Wouldn't this be an inspector nightmare though? I might be misunderstanding your advice here, but if each Item had a list of properties, then I'd need to go to each property and edit them there rather than in the Item's inspector. Also if the properties are SOs wouldn't I need a new one for every item? Like each weapon would need its own damage PropertySO?
     
  5. PaperMouseGames

    PaperMouseGames

    Joined:
    Jul 31, 2018
    Posts:
    434
    Hey thanks for the reply, I haven't heard of SerializeReference so I'm looking it up now, what do you mean by your edit though? That went over my head but maybe it's because I'm unfamiliar with SerializeReference.
     
  6. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    @PaperMouseGames

    I meant that you seem to be using Scriptable Objects, so if you setup a list out of those, there are no issues (with subclasses). If you have a list of Scriptable Objects in your inspector list, you can simply drag and drop your Item subclass Asset files into such list, as Scriptable Objects and MonoBehaviours too support this.

    So it works like this:
    upload_2022-2-16_17-43-1.png

    But for a List made out of Serializable classes marked as SerializeReference this isn't an option.
     
    Last edited: Feb 16, 2022
  7. _met44

    _met44

    Joined:
    Jun 1, 2013
    Posts:
    633
    First, a quick note: you can always embed an inspector within another, if you use odin its an attribute to add, otherwise its still just a single line of editor code.

    I didn't go into details, but yeah SO would identify the property, and you would have to serialize the specific value for the item in the item property list.

    So the list wouldn't directly reference property SOs, but instead be a data holding serializable class with the property SO ref and the value (a quick editor magic can let you display only the data type you need in case you need several):

    Code (CSharp):
    1. [System.Serializable]
    2. public class ItemProperty
    3. {
    4.   [SerializeField] PropertySo _property; //
    5.   [SerializeField] int _intVal;
    6.   [SerializeField] float _floatVal;
    7.  
    8.   public PropertySo Property => _property;
    9.   public int IntVal => _intVal;
    10.   public float FloatVal=> _floatVal;
    11. }
    If unused fields in editor trouble you, you can use some editor magic to hide _intVal or _floatVal (or both) based on a value of the property for example.

    The consumming logic should be aware of the type it expects and can use one or the other value based on what makes sens to the feature.

    For example the item API could provide a method such as item.TryGetIntPropertyValue(property, out var intPropertyVal) so you can poll your items for specific properties, thus giving you both the information of whether the property is set for the item or not, and its value.
     
    eses likes this.
  8. PaperMouseGames

    PaperMouseGames

    Joined:
    Jul 31, 2018
    Posts:
    434
    Thanks to everyone who helped out here! I ended up getting a pretty good solution with some of the advice here and a custom editor.

    Here is my code:

    Code (CSharp):
    1. [CreateAssetMenu(fileName = "New Data", menuName = "Data/DataSO")]
    2.     public class DataSO : ScriptableObject
    3.     {
    4.         [SerializeField] string itemName = "";
    5.  
    6.         [SerializeField] ItemType itemType;
    7.  
    8.         [SerializeField] PotionProperties potionProperties;
    9.         [SerializeField] WeaponProperties weaponProperties;
    10.  
    11.         [SerializeField] ItemTypeOrganizer itemTypeOrganizer;
    12.  
    13.         public ItemType ItemType { get => itemType; }
    14.         public PotionProperties PotionProperties { get => potionProperties; }
    15.         public WeaponProperties WeaponProperties { get => weaponProperties; }
    16.         public ItemTypeOrganizer ItemTypeOrganizer { get => itemTypeOrganizer; }
    17.     }
    18.  
    19.  
    20.     [CustomEditor(typeof(DataSO))]
    21.     [CanEditMultipleObjects]
    22.     public class DataSOEditor : Editor
    23.     {
    24.         SerializedProperty potionProperties;
    25.         SerializedProperty weaponProperties;
    26.  
    27.         SerializedProperty itemType;
    28.  
    29.         SerializedProperty itemTypeOrganizer;
    30.  
    31.         private void OnEnable()
    32.         {
    33.             potionProperties = serializedObject.FindProperty("potionProperties");
    34.             weaponProperties = serializedObject.FindProperty("weaponProperties");
    35.  
    36.             itemType = serializedObject.FindProperty("itemType");
    37.  
    38.             itemTypeOrganizer = serializedObject.FindProperty("itemTypeOrganizer");
    39.         }
    40.         public override void OnInspectorGUI()
    41.         {
    42.             if (itemTypeOrganizer.objectReferenceValue == null)
    43.             {
    44.                 EditorGUILayout.PropertyField
    45.                     (itemTypeOrganizer, new GUIContent("Item Type Organizer"));
    46.             }
    47.             else
    48.             {
    49.                 EditorGUILayout.PropertyField
    50.                         (itemType, new GUIContent("Item Type"));
    51.  
    52.                 var test = itemType.objectReferenceValue;
    53.                 ItemType itemTypeTest = (ItemType)itemType.objectReferenceValue;
    54.  
    55.                 ItemTypeOrganizer organizer = (ItemTypeOrganizer)itemTypeOrganizer.objectReferenceValue;
    56.  
    57.                 if (organizer.IsItemTypePotion(itemTypeTest))
    58.                 {
    59.                     EditorGUILayout.PropertyField
    60.                         (potionProperties, new GUIContent("Potion Properties"));
    61.                 }
    62.                 else
    63.                 {
    64.                     EditorGUILayout.PropertyField
    65.                         (weaponProperties, new GUIContent("Weapon Properties"));
    66.                 }
    67.  
    68.                 EditorGUILayout.PropertyField
    69.                     (itemTypeOrganizer, new GUIContent("Item Type Organizer"));
    70.             }
    71.             serializedObject.ApplyModifiedProperties();
    72.         }
    73.     }
    So the names are a little funny and I only have 2 types of items but this is because I created some new scripts to see if I liked the results of this solution.

    So what I'm doing is turning a lot of the values in the original
    ItemSO
    class (represented here by
    DataSO
    ) into classes. So instead of
    ItemSO
    having
    int damageAmount
    and
    int healAmount
    , etc. Those values are now encapsulated under the
    WeaponProperties
    and
    PotionProperties
    classes.

    This works really well, and even without the custom editor it's much nice to look at when building items in the inspector.

    Additionally, in the editor script, now there are conditions which can filter what the inspector is showing.

    The
    ItemTypeOrganizer
    class simply holds all the
    ItemType
    SOs and has methods to check if any passed in
    ItemType
    is of a particular type.

    Once that checks the types, it returns a boolean, and the editor works great like this.

    The only real issue is there is a lot of repeated code in the
    ItemTypeOrganizer
    since it has to check if a type is a particular kind.

    So there is a method like this:

    Code (CSharp):
    1. public bool IsItemTypePotion(ItemType itemType)
    2.         {
    3.             if (itemType == potion)
    4.             {
    5.                 return true;
    6.             }
    7.             return false;
    8.         }  
    For every single
    ItemType
    . I'll see if I can optimize that, but if anyone has ideas I'd love to hear them
     
  9. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    "For every single ItemType . I'll see if I can optimize that, but if anyone has ideas I'd love to hear them"

    I think you could at least make those methods into expression bodied methods. I'm not too sure about syntax without testing but something like this should work. And you already have the result based on your == check:
    Code (CSharp):
    1. // Instead of this:
    2. public bool IsItemTypePotion(ItemType itemType)
    3. {
    4.     if (itemType == potion)
    5.     {
    6.         return true;
    7.     }
    8.     return false;
    9. }
    10.  
    11. // Do this for each:
    12. public bool IsItemTypePotion(ItemType itemType) => itemType == potion;
    13. public bool IsItemTypeFood(ItemType itemType) => itemType == food;
    14.