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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

RPG Inventory - ScriptableObject List or List of ScriptableObjects?

Discussion in 'Scripting' started by Duffer123, Dec 4, 2015.

  1. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,215
    Either way so much of the code above is so very useful to me and it's helped hone my ideas some more.
     
  2. Nanospin

    Nanospin

    Joined:
    Jul 9, 2014
    Posts:
    19
    First I am sorry to revive this almost old topic,
    But I wanted to thank for the explanations and the quality of the provided help.

    I recently open my eyes on serialization and scriptableObject as I created a polymorphism system to describe my inventory. In my game, the character is a spaceship and has member such as a human that you can equip with systems. At the moment my working system use prefabs and classical class polymorphism. It's cool but I realize the limitation amd I would like more convenient and data oriented approach.

    I am a beginner so my understanding is quite limited but with the previous system described by Tony you may end up with numerous asset describing numerous Attributes. Each time you need to define an attribute for an item you have to create it and save it as an asset because it is a scriptableObject field and it is not serialized.
    For exemple, if I create a sword that give 20 damage, I will have to create and save in the assets a Damage20 object which is a DamageAttribute with a numerical value of 20.
    On the other hand, unity doesn't allow to serialize class using polymorphism and I didn't find any simple alternative.

    So to avoid creating as many asset as there is existing different attributes, Tony you propose to create a attributes based on primary datatypes, such as floats, int etc... I can see roughly what it should do, but in practice I am struggling to apply this idea. Could you illustrate this way with a basic example ?

    Thank you very much for your help.
     
  3. LeftyRighty

    LeftyRighty

    Joined:
    Nov 2, 2012
    Posts:
    5,148
    TonyLi likes this.
  4. Nanospin

    Nanospin

    Joined:
    Jul 9, 2014
    Posts:
    19
    Thanks a lot,
    I am looking forward to watch it :)
     
  5. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,533
    I was going to point you to code in a previous post, but the upcoming live training videos look they should answer all your questions.
     
  6. Nanospin

    Nanospin

    Joined:
    Jul 9, 2014
    Posts:
    19
    Thanks Tony,

    I applied the idea in the code you point. But I wasn't very keen to create multiple asset of a same damageAttribute when only the numerical value changes. And I understood that reversing the problem: i.e. creating for instance a floatAttribute might help.

    But I wonder if the behavior I expect is not a serializedObject one, and in that case, polymorphism doesn't work ( so no list of attributes) as you explained before.


    EDIT:

    Ok I am sorry, I realized that I misunderstood something.
    Creating a new item with a new attribute, does not create a new attribute asset in the asset rep.
    Using the unity editor and inspector to create new item oblige to create manually a new attribute and reference it to the item. The custom inspector you gave us avoid that.
     
    Last edited: Aug 4, 2016
  7. Slyder

    Slyder

    Joined:
    Oct 17, 2013
    Posts:
    270
    I hate to be the Debbie-downer here but this is an absolutely over-complicated way of managing your items and it goes against almost all of the principles of OOD.

    Every Weapon IS an Item.

    Every Item probably has an ItemName and ItemWeight.....you don't need an ItemAttribute class for this.
    Every Weapon probably has a MinDamage and MaxDamage.....you don't need an ItemAttribute class for this.

    You can just do something like:
    Code (csharp):
    1.  
    2. [System.Serializable]
    3. public class ItemType : ScriptableObject {
    4.  
    5.     public Sprite itemIcon;
    6.     public string itemName;
    7.     public int itemWeight;
    8. }
    9.  
    10. [System.Serializable]
    11. public class WeaponType : ItemType {
    12.     public int minDamage;
    13.     public int maxDamage;
    14.     public int maxDurability;
    15. }
    16. public class ItemInstance {
    17.     public ItemType itemType;
    18.     public int itemID; //set this to a unique ID on pickup to distinguish it from others of same itemType
    19. }
    20. public class WeaponInstance : ItemInstance {
    21.     protected int m_durability = null;
    22.     public int durability {
    23.         get {
    24.             if(m_durability == null)
    25.                  m_durability = (WeaponType)itemType.maxDurability;
    26.             return m_durability;
    27.         }
    28.         set { m_durability = value; }
    29.     }
    30. }
    31.  
    32. public class Inventory : MonoBehaviour {
    33.     public List<ItemInstance> items = new List<ItemInstance>(); //or any other datastructure
    34. }
    35.  

    Create your Item and Weapon Assets in the Asset database. Set their respective values.
    Your inventory would be data structure of ItemInstance

    Add "ItemIdentifiers" to your objects in the world that hold what ItemType it is so you can manage the ItemInstances as you equip and pickup different items.

    Viola. Inventories are not that complicated. The more difficult part is creating a nice functional UI to represent your Inventory.
     
    Last edited: Aug 18, 2016
    xNex and Duffer123 like this.
  8. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,215
    As ever I go with very data rich items referencing all sorts of things like restricted classes, mesh, materials, rarity, loot info etc etc...
     
  9. Vedrit

    Vedrit

    Joined:
    Feb 8, 2013
    Posts:
    514
    I opted for a list of items, each of which used item attributes that pulled from the same class as the list, so that everything was easily comparable to eachother.
    E.G.
    Item:
    itemList.list itemInfo = new itemList.list();
    itemInfo.stat1 = blah;
    itemInfo.stat2 = blah;
    itemInfo.statn = blah;

    Inventory:
    List<itemList.list> contents = new List<itemList.list>();

    contents.Add(itemInfo);

    Boom! Easily add, remove, and compare information!
     
  10. Slyder

    Slyder

    Joined:
    Oct 17, 2013
    Posts:
    270
    What your style of solution is missing is that your items do not all function the same way. Items are not just stats. You cannot perform the same actions to every item in your inventory; craft, consume, equip, etc...

    This is an over-engineered solution that ends up being a hackish mess as you try to add more complex functionality to different types of items for most inventory item instances.

    If I have an ItemType class and extend that into other Types...such as PotionType, I can have a OnUse() function either through an interface or the inheritance path.
     
    Last edited: Aug 18, 2016
  11. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,215
    @Slyder ,

    I see where you're coming from - but how does your way of doing it all cope with:-

    a) items with potentially lots of different properties? and

    b) using TonyLi's example, say a Shield that can also be used to 'bash' as a Weapon - so item 'types' that cross over different areas and seemingly wreck the structure using Inheritance?
     
  12. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,533
    I definitely agree with Slyder that a composition-based solution is overkill if you only need to keep track of weight, cost, etc. One definition of elegant design is using the simplest solution that accomplishes the goal. But, as Duffer writes, it's insufficient when you need to add unique, arbitrary code-based behavior to different elements, especially if you don't know all those behaviors at initial design time.

    Inheritance versus composition has been discussed to death, including advantages and disadvantages to each. It's worth googling if anyone has a question about which way to go. Generally speaking, the design trend for attribute-based systems is composition. In fact, Unity just released a tutorial on this:

    Ability System with ScriptableObjects
     
  13. Slyder

    Slyder

    Joined:
    Oct 17, 2013
    Posts:
    270
    ItemTypes are not rigid. The best way to use them is in a very broad way so your Asset creation can cover many different items. When it boils down to it, you probably won't have more than 4 or 5 different types of items in your inventory. If you do, that's fine though.

    2 options:

    1) interfaces if you have a more rigid class structure.
    or
    2) Create some unique ItemType variation that can be equipped and used as a weapon and use that Asset to make your Shield

    This setup is not one of rigid inheritance; ie every item having their own script. The goal is to make very broad Types that cover many items.
     
  14. Slyder

    Slyder

    Joined:
    Oct 17, 2013
    Posts:
    270
    I'm actually going to delete some of my other posts because this is a pretty cool approach and I can see where it could be useful. I would probably use a combination here.

    Inheritance to cover the very broad shared attributes.
    Components to teach my Individual Assets how to do different things.

    I think it kind of depends on where the functionality sits. In an FPS, you probably have an actual First Person version of a weapon that holds all the functionality. The Inventory in this case, only needs to know that it has some weapon in the inventory. An RPG on the other hand, has potions and scrolls and things that you could make do some really cool things with a very broad component approach.

    If we look at the Survival genre, It mixes some elements like medical items and weapons so I can see the component system being beneficial within the inventory here as well.

    Going to explore this a bit.
     
    Last edited: Aug 18, 2016
    Chapmanly likes this.
  15. Vedrit

    Vedrit

    Joined:
    Feb 8, 2013
    Posts:
    514
    @Slyder I said "stats" but that was not a good word to use. "Property" would have been better. One of the properties is item type, which will dictate whether it has an onClick at all. Is it a bag type? Scroll type? 1-handed weapon? Potion?
    I understand that some may not like this method, and it may not be as efficient or clean as others, but I like it.
     
  16. Slyder

    Slyder

    Joined:
    Oct 17, 2013
    Posts:
    270
    Yeah I see where this approach is ideal for functionality.

    I actually used this concept to build an Interaction system in UE4 (trying to learn both engines)

    So anything with "Interactable" can have a List of Actions that happen when Interacted with, but you interact with everything the same way.
     
  17. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    5,996
    This is what I was looking for.
    I'll try to make it Property Drawer sauce so we don't need the DoLayout... stay tuned
     
  18. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,215
  19. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    5,996
    :D someone knows a thing or two about banging head against custom inspector.
    I've tried to shoehorn some of this editor.OnInspectorGUI (); goodness into @TonyLi custom inspector but I might need to actually learn custom inspector and ... meh.

    But I found a nice reorderlist that don't require change of data type to work :D https://forum.unity.com/threads/what-is-the-best-way-to-do-a-reorderable-list.511070/
     
  20. Lonelywolf00

    Lonelywolf00

    Joined:
    May 15, 2015
    Posts:
    1
    Hello guys!

    Big shoutout to @TonyLi amazing assets man, and proffessional work, I bought what I could find useful and I'm digging and using them 24/7.

    I would like to complete the scripts previously posted about showing people how to serialize ItemAttributes without creating physical assets in the project folders. This was the missing puzzle piece logic I was searching for in the past week to truly shape up my inventory system.

    The delete code tho has a flaw, in which the old item attributes data "leaks" - it is still attached to the game object, increasing its size, even though it is properly deleted from the item.attributes list.

    To properly delete an Item attribute, we need to "undo" the AddObjectToAsset magic trick:

    Code (CSharp):
    1. if (indexToDelete > -1)
    2.             {
    3.                 //undo the AssetDatabase.AddObjectToAsset called from the Add button
    4.                 UnityEngine.Object.DestroyImmediate(item.attributes[indexToDelete], true);
    5.                 item.attributes.RemoveAt(indexToDelete);
    6.                 AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(item));
    7.                 AssetDatabase.SaveAssets();
    8.                 AssetDatabase.Refresh();
    9.             }
    Thank you so much for the Add method, really finally understood how this system can actually be used properly.. and I'm ashamed I didn't find this trick anywhere.. shipped a game to steam and android in the meantime and spent years developing in unity as a hobby... lol. But never used Scriptable Objects like this.

    Thank you, you are an eye opener.
    -Lon

    P.S. This is also my first post :O
     
    eraserlark and TonyLi like this.
  21. eraserlark

    eraserlark

    Joined:
    Oct 2, 2020
    Posts:
    1
    Hi, @TonyLi I have a quick question, I was seeing you create public lists like 'List<item> items' and ' List<ItemAttribute> attributes', but I'm unclear how you initialized those lists. For things like List<ItemAttribute> attributes, would you need to initialize every attribute an item would have (i.e. DamageAttribute, CostAttribute, etc.) in there at Start()? For every item that uses attributes?

    I'm still relatively new to C# and Unity, but thank you for writing this out this looks super helpful!
     
  22. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,533
    @eraserlark - If you're referring to this post from 2015, it assumes you're going to set them manually in the Unity editor's inspector. ScriptableObjects are basically just regular C# objects that Unity can serialize (i.e., save and restore values). Since they're serializable, Unity can create assets out of them and show their values for editing in the inspector.
     
    eraserlark likes this.
  23. stain2319

    stain2319

    Joined:
    Mar 2, 2020
    Posts:
    417
    Since this thread has already been necro'd... :)

    I just want to make sure I am understanding this correctly as I think it seems very useful.

    Simplifying things a bit, suppose in my game I have three items that the user could have in his inventory. We'll say they are, for example, a torch, a shovel, and a rock.

    Let's suppose a torch weighs 1, a shovel weighs 5, a rock weighs 6.
    Each one is represented in player's inventory by an icon
    each one also has a "durability" value, let's say 10, 20, and 50 respectively

    So I would essentially need:

    a WeightAttribute scipt
    an IconAttribute script
    a DurabilityAttribute script

    editor scripts for each of the above to create the actual assets.

    using those editor scripts I would need e.g. the following assets to be created as these types of attributes:

    Weight1 asset
    Weight5 asset
    Weight6 asset

    torchIcon asset
    shovelIcon asset
    rockIcon asset

    Durability10 asset
    Durability20 asset
    Durability50 asset

    Then I would create a "Torch" Item (using the editor script again) and that Item would simply be a List of 3 attributes:
    Weight1
    torchIcon
    Durability10

    Is that basically the way of it?
     
  24. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,533
    If those are the main values, it would be simpler to just put them in a single script. But let's say each item can also have a "use ability" -- something special that happens when the player uses the item. You could slot in a separate ScriptableObject for that. Examples:

    ScriptableObject script that you can use to make "item template" assets:
    Code (csharp):
    1. public class ItemTemplate : ScriptableObject
    2. {
    3.     public float weight;
    4.     public Sprite icon;
    5.     public float maxDurability;
    6.     public Ability useAbility;
    7. }
    Base ScriptableObject script that you can use to define a use ability:
    Code (csharp):
    1. public abstract class Ability : ScriptableObject
    2. {
    3.     public abstract void Use(GameObject user);
    4. }
    Then an actual item that can exist at runtime:
    Code (csharp):
    1. public class Item
    2. {
    3.     public ItemTemplate templateAsset; // Where we get static attributes such as weight and icon.
    4.     public float currentDurability;
    5. }
    Example use ability:
    Code (csharp):
    1. public class ExplodeAbility : Ability // This is really just a generic "spawn prefab" ability.
    2. {
    3.     public GameObject explosionPrefab;
    4.  
    5.     public override void Use(GameObject user)
    6.     {
    7.         Instantiate(explosionPrefab, user.transform);
    8.     }
    9. }
    So you can see that you have a slottable "use ability" for flexibility, but durability is implemented in a much simpler way. Keep things simple, and only add complexity where necessary.

    The code above doesn't use any editor scripts. It all runs on default Unity inspectors.
     
    stain2319 likes this.
  25. stain2319

    stain2319

    Joined:
    Mar 2, 2020
    Posts:
    417
    Thanks.

    So with this system suppose I have some items that can be used and some that can't, I assume it's just a matter of my UI code checking items for the presence of the Use ability and using that to decide whether to display the "use item" button (for example)?
     
  26. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,533
    That's how I'd do it.