Search Unity

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,032
    Not much to add to subject line question really. Is it better to create a large number of scriptableobject items and then a non-scriptableobject list of them as an inventory or whatever or it better to have the list itself as the scriptableobject? What do people think? Why?

    If you go with the ScriptableObject List route, is it still easy to extract and instantiate the individual items (which could contain classes, types, unity engine types and sub lists and collections themselves...) within the scriptableobject list as GameObjects (or whatever) in their own right?

    (hope someone knows what I am getting at!)
     
  2. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    9,524
    My recommendation is a list of ScriptableObjects. This way you can subclass them and Unity will still serialize a list of them properly.
     
  3. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,032
    @TonyLi,

    Many thanks for responding. . Could you give me an example of how to do subclasses with say items. . What class is the scriptableobject and how would you code that? . (many thanks in advance for any pointers you can give...)
     
  4. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    9,524
    I tend to use composition over inheritance. They each have their proper place, but composition works well for things with attributes, such as items and skills. If you did only inheritance, the subclasses might look like this:
    • Item : ScriptableObject (name, weight, cost, etc.)
      • Weapon : Item (damage, to-hit, etc.)
        • MeleeWeapon : Weapon
        • RangedWeapon : Weapon
      • Armor : Item (AC bonus, etc.)
        • Shield : Armor
        • Helmet : Armor
        • Suit : Armor
    If your character has a MonoBehaviour with a list of Items:
    Code (csharp):
    1. public List<Item> items;
    then you can assign any type of Item (MeleeWeapon, Shield, etc.) and Unity will serialize and deserialize it properly.


    However, you run into problems with inheritance when you have a SpikedBuckler that's a Weapon and Armor. Do you make it a subclass of Weapon or a subclass of Armor? If you make it a Weapon, you need to define an AC bonus -- but now you've defined two different AC bonus variables in two separate places (Armor and SpikedBuckler). You could instead move AC bonus, damage, etc., to the parent Item class, but then why have subclasses at all? And items like Helmets inherit attributes such as damage that make no sense.

    This is why I prefer composition. It's a little more complicated to set up initially, but it's cleaner and more flexible:
    Code (csharp):
    1. public class Inventory : MonoBehaviour {
    2.     public List<Item> items;
    3. }
    4.  
    5. public class Item : ScriptableObject {
    6.     public List<ItemAttribute> attributes;
    7. }
    8.  
    9. public abstract class ItemAttribute : ScriptableObject {
    10. }
    You'd then create subclasses of ItemAttribute for attributes such as weight, cost, damage, AC bonus, etc.

    When you create an instance of Item, you add whatever ItemAttributes it needs.
     
    phobos2077, Duffer123, Isola and 5 others like this.
  5. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    6,616
    Wouldn't it depend on the situation.

    If you need a List of SomeTypeX, or SomeTypeX to have a List?

    Like this is such an abstract dependency chain, that I could see either or being a thing.
     
  6. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    9,524
    Yeah, I get easily sidetracked onto my composition soapbox. ;-)

    My real point was that a list of ScriptableObjects serializes well.
     
    laurentlavigne likes this.
  7. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,032
    @TonyLi,

    Your posts are enormously helpful.

    Why is the ItemAttribute class abstract? Why is it a scriptableobject?

    Also, presumably, in your example, I'd put say itemID and itemName types in the item class but pretty much everything else in descriptively named 'ItemAttribute' classes which could each encompass a set of flags or say a sprite itemIcon or another deeper collection.

    However, how would you search say Inventory quickly for say Items with an ItemAttribute class named xxx or, worse yet, how would you search Inventory for items for ones with ItemAttribute class named xxx which had an internal variable or type with value yyy , if you see what I mean...

    Also, coming back to your original Inheritance example, if I ctrl+d and create a new item scriptableobject how does it know whether it's a melee weapon or a shield or a helm, so to speak?
     
    Last edited: Dec 7, 2015
  8. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    9,524
    Hi,
    It's abstract because it's an empty stub. The idea is that you'll make subclasses such as DamageAttribute:
    Code (csharp):
    1. public class DamageAttribute : ItemAttribute {
    2.     public float minDamage;
    3.     public float maxDamage;
    4. }
    Making the base class abstract prevents you from being able to create an instance of it, since you should only create instances of the subclasses that actually contain data.

    It's a ScriptableObject because Unity can serialize subclasses properly in a list:
    Code (csharp):
    1. public List<ItemAttribute> attributes;
    If ItemAttribute and its subclasses were not ScriptableObjects, then when Unity deserialized the elements of the list (i.e., loaded them back from disk), it would create them all as the base type, ItemAttribute, not their correct subclass types.

    Depends on what you'd need to do. You could index it -- for example, create another list called damageItems that contains references to all items that have the DamageAttribute in their attributes list. Typically the number of items on a character isn't so high that you need to index everything ahead of time. You could probably get by with a fancy, one-line Linq statement.

    You can create ScriptableObjects in memory just like an instance of any other class. But you can also save them as asset files, which is what you're talking about. The Unity community wiki has a CreateScriptableObject script that creates a ScriptableObject instance in memory and then saves it as an asset file. You can adapt this to create empty Item assets. You could also adapt it to create assets of the various attribute types (DamageAttribute, etc.). Then you can assign the attribute assets to the Item asset's attributes list. If you then select the Item asset and press Ctrl+D, it will copy the asset including all of its references to the attribute assets.

    Alternatively, if you don't want to keep track of so many attribute assets, you could write a custom editor for Item that creates memory-only instances of the various attribute types and adds them directly to the Item's attributes list.
     
  9. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,032
    Hi TonyLi,

    Once again, many thanks. . I'll give some of this a try. . I like this Item Attributes thing and will give it a try as my current Item ScriptableObjects have a huge number of fields which I'm using custom editor to try and tidy up..
     
    hacankt likes this.
  10. ung

    ung

    Joined:
    Feb 22, 2015
    Posts:
    154
    If the point is composition you could just use GameObjects then.
     
  11. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,032
    I'm not so sure. . I love the idea of only attaching the attributes I want to, say, items... And ScriptableObjects are much much better than stuffing GOs with data in terms of persistence and memory and speed...
     
  12. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    9,524
    That's a good point. The major difference between a GameObject and a ScriptableObject is that a GameObject lives in a scene (or is a prefab that can be instantiated into a scene), whereas a ScriptableObject doesn't have to be tied to a scene. Sometimes it's just more convenient or more comfortable to build things in a scene and then save them as prefabs, so for the most part this might just be personal preference.

    FWIW, in similar discussions on skill systems (which are very similar to inventory systems in design), I previously provided a GameObject prefab example and a ScriptableObject example in case they're of any help to anyone.
     
  13. ung

    ung

    Joined:
    Feb 22, 2015
    Posts:
    154
    And actually I usually just use a ScriptableObject with all the attributes regardless of the object type.
     
  14. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    9,524
    It's certainly simpler. I prefer to add only the attributes that each item needs. It also usually makes it so I don't have to write a custom editor, since the default editor shows just the variables for each attribute subclass instance. But again it really depends on your needs.
     
  15. ung

    ung

    Joined:
    Feb 22, 2015
    Posts:
    154
    Yes, I forgot to mention that I also use a custom inspector to only show relevant attributes.
     
    TonyLi likes this.
  16. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,032
    The posts on this thread are v useful to relative c# and Unity noobs like me!
    Thanks both.
     
  17. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,032
    @TonyLi,

    Could you be persuaded to post some pseudo-code to show how you would go about searching for Items with Item Attribute fields carrying certain values?
     
  18. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    9,524
    Consider this definitely pseudocode. I'm out of the office right now and can't test the syntax.
    Code (csharp):
    1. var weapons = items.FindAll(item => (item.Find(attribute => attribute.GetType() == typeof(DamageAttribute)) != null))
     
  19. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,032
    Hah! . Still it gets me where I need to be in understanding this. . Excellent!
     
  20. ung

    ung

    Joined:
    Feb 22, 2015
    Posts:
    154
    My own example. Lacks a lot of documentation and there's no error checking. It's just to show you that there are a lot of neat things that you can do with your items.

    Code (CSharp):
    1. /// <summary>
    2. /// Base class for item attributes.
    3. /// </summary>
    4. public class ItemAttribute
    5. {
    6.     // ...
    7. }
    8.  
    9.  
    10. /// <summary>
    11. /// An example attribute storing the "Attack Power" of a weapon.
    12. /// </summary>
    13. public class WeaponAttribute : ItemAttribute
    14. {
    15.     public float Power;
    16. }
    17.  
    18.  
    19. /// <summary>
    20. /// An Item is a simple collection of attributes.
    21. /// </summary>
    22. public class Item
    23. {
    24.     private IDictionary<Type, ItemAttribute> m_Attributes;
    25.  
    26.     public Item()
    27.     {
    28.         m_Attributes = new Dictionary<Type, ItemAttribute>();
    29.     }
    30.  
    31.     public void Add(ItemAttribute attribute)
    32.     {
    33.         m_Attributes[attribute.GetType()] = attribute;
    34.     }
    35.  
    36.     public TAttribute GetAttribute<TAttribute>() where TAttribute : ItemAttribute
    37.     {
    38.         ItemAttribute attribute;
    39.  
    40.         if (m_Attributes.TryGetValue(typeof(TAttribute), out attribute))
    41.         {
    42.             return (TAttribute)attribute;
    43.         }
    44.  
    45.         return null;
    46.     }
    47. }
    48.  
    49.  
    50. /// <summary>
    51. /// A Database storing all items in our game.
    52. /// </summary>
    53. public class ItemDatabase
    54. {
    55.     public IList<Item> Items { get; private set; }
    56.  
    57.     public ItemDatabase()
    58.     {
    59.         Items = new List<Item>();
    60.     }
    61.  
    62.     public Item FindWithAttributeValue<TAttribute, TValue>(Func<TAttribute, TValue> valueGetter, TValue value)
    63.         where TAttribute : ItemAttribute
    64.     {
    65.         return Items.FirstOrDefault(item =>
    66.         {
    67.             var attribute = item.GetAttribute<TAttribute>();
    68.  
    69.             if (attribute != null)
    70.             {
    71.                 return value.Equals(valueGetter((TAttribute)attribute));
    72.             }
    73.  
    74.             return false;
    75.         });
    76.     }
    77. }
    78.  
    79. public class MyScript : MonoBehaviour
    80. {
    81.     public ItemDatabase ItemDatabase;
    82.  
    83.     public void UsageExample()
    84.     {
    85.         // Finds an Item having a WeaponAttribute with a Power value of 5.5f
    86.         var weapon = ItemDatabase.FindWithAttributeValue<WeaponAttribute, float>(
    87.             attribute => attribute.Power, 5.5f);
    88.     }
    89. }
     
  21. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,032
    @ung,

    Thanks. . Much to consider. Why not scriptableobject and why IList not List? Also none of it is permanently storing data? Are you shoving a key class to a game object and saving to a prefab? Is that certain bit of code addressing the null fields problems?
     
  22. ung

    ung

    Joined:
    Feb 22, 2015
    Posts:
    154
    It may be useful making ItemDatabase a ScriptableObject and the rest Serializable. That depends a bit on the way you want to store the data. The above code is more focused on your previous question (how to find an item by one of its attributes).

    Edit: actually an Item should also be a ScriptableObject.

    It's good practice to always expose the less specific type as possible. Allows to more easily change implementation later on if needed (i.e., replace List<T> with another collection type implementing the same interface without breaking client code).

    As mentioned above ItemDatabase can be made a ScriptableObject. Maybe "custom" serialization would be a better choice given the complexity of the data graph. Again, the above is an example focused on another aspect of the problem.

    Not sure what you mean but you may use prefabs for items. Not recommended even if I talked about it in a previous post. The reason it's not a good idea is because you are really representing an item's data and not the item itself.

    Could you be more specific?
     
  23. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,032
    @ung ,

    Thanks for that. Re the Null thing I was referring to this bit of your code (and I was remembering that in some circs there is a problem with serialising/deserialising null fields in lists?):-

    Code (CSharp):
    1.     public Item FindWithAttributeValue<TAttribute, TValue>(Func<TAttribute, TValue> valueGetter, TValue value)
    2.         where TAttribute : ItemAttribute
    3.     {
    4.         return Items.FirstOrDefault(item =>
    5.         {
    6.             var attribute = item.GetAttribute<TAttribute>();
    7.             if (attribute != null)
    8.             {
    9.                 return value.Equals(valueGetter((TAttribute)attribute));
    10.             }
    11.             return false;
    12.         });
    13.     }
     
  24. Wolv15

    Wolv15

    Joined:
    Nov 21, 2015
    Posts:
    5
    Create class where you store all your items(Prefabs, iteminfo)
    Each item has own id.
    Inventory is a list of object with only 2 variables: id and amount. Every time you want to display info you refering to class that have all items. I would do it like this. :|
     
  25. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,032
    @TonyLi,

    Further dumb question. . Using the scriptableobject and ItemAttribute route, how do you go about, in runtime in c#, a) creating a new item asset to add to your collection with its own item attributes and b) how do you go about altering an existing item asset in terms of its attributes and their vales??
     
  26. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    9,524
    Hi - Use the CreateScriptableObjectAsset script, and define a few menu items in a class:
    Code (csharp):
    1. public class MenuItems {
    2.  
    3.     [MenuItem("Assets/Create/Duffer123/Item")]
    4.     public void CreateItem() {
    5.         ScriptableObjectUtility.CreateAsset<Item>();
    6.     }
    7.  
    8.     [MenuItem("Assets/Create/Duffer123/Attributes/Damage Attribute")]
    9.     public void CreateDamageAttribute() {
    10.         ScriptableObjectUtility.CreateAsset<DamageAttribute>();
    11.     }
    12.  
    13.     [MenuItem("Assets/Create/Duffer123/Attributes/Name Attribute")]
    14.     public void CreateNameAttribute() {
    15.         ScriptableObjectUtility.CreateAsset<NameAttribute>();
    16.     }
    17. }
    When you start up a MonoBehaviour at runtime, I recommend instantiating a copy of the ScriptableObject. Otherwise you'll end up changing the actual disk copy. Something like:

    Code (csharp):
    1. public class Inventory : MonoBehaviour {
    2.  
    3.     public List<Item> items;
    4.  
    5.     void Start() {
    6.         for (int i = 0; i < items.Count; i++) {
    7.             items[i] = Instantiate(items[i]);
    8.         }
    9.     }
    10.  
    11.     void DevalueEverything() {
    12.         foreach (item in items) {
    13.             var cost = item.attributes.Find(attribute => attribute.GetType() == typeof(CostAttribute));
    14.             if (cost != null) cost.dollarAmount /= 2;
    15.         }
    16.     }
    17. }
     
  27. ung

    ung

    Joined:
    Feb 22, 2015
    Posts:
    154
    I check for null ref because in my example Item.GetAttribute returns null if that particular Item doesn't have an attribute of the requested type.

    @TonyLi Don't you think attributes should really be just Serializable POCOs?
     
  28. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    9,524
    Unity doesn't properly deserialize subclasses if they're in a list. In the Serialization in Unity blog post, scroll down to No suppot for polymorphism. Take this example:
    Code (csharp):
    1. [Serializable]
    2. public class ItemAttribute {}
    3.  
    4. [Serializable]
    5. public class DamageAttribute : ItemAttribute {
    6.     public float minDamage, maxDamage;
    7. }
    8.  
    9. [Serializable]
    10. public class NameAttribute : ItemAttribute {
    11.     public string name;
    12. }
    13.  
    14. public class Inventory : MonoBehaviour {
    15.     public List<ItemAttribute> items;
    16. }
    If you were to add a DamageAttribute and a NameAttribute to Inventory.item:
    Code (csharp):
    1. var inventory = GetComponent<Inventory>();
    2. inventory.items.Add(new DamageAttribute());
    3. inventory.items.Add(new NameAttribute());
    The items list will contain one object whose type is DamageAttribute and another whose type is NameAttribute -- until you serialize the component to disk and then deserialize it back into memory. Unity will deserialize it back as a list of two elements of the base ItemAttribute type.

    A list of ScriptableObjects, on the other hand, serializes and deserializes properly. You don't have to save the ScriptableObjects as asset files; you can just create them in memory and add them to the list.
     
  29. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,032
    @TonyLi,

    Thanks yet again. .

    That bit about nulls in lists was what I was struggling to say to Ung.

    The second bit of your code is vv helpful as I really struggle with code for sifting lists for certain items etc.

    What if I use the Base asset items in my game and I have other say weapons with more or different attributes in my characters' (in the plural) inventories etc. . What is the best way to save out those new and different items?

    I had this crazy idea of saving out new items with new attributes, say, as assets to disk at runtime. Can this actually be done? . Should it? . What's the best method to save out these lists at runtime if I'm using in the first place your ItemAttribute thing...?
     
  30. ung

    ung

    Joined:
    Feb 22, 2015
    Posts:
    154
    Read the infos, I admit I'm not very experienced with the built-in serialization yet, thank you very much.
     
  31. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    9,524
    Hi - At design time, you can just create new ScriptableObjects assets and customize them. At runtime, use a plugin to serialize your objects to a saveable format like binary, XML, or JSON. The Asset Store has several free and paid options.
     
  32. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,032
    @TonyLi,

    Should you not change ScriptableObjects at runtime them? Do you rather serialise and save out various classes and lists?
     
  33. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    9,524
    Say you have an item asset file, and you've assigned it to primaryHand on your player character GameObject here:
    Code (csharp):
    1. public class Player : MonoBehaviour {
    2.     public Item primaryHand; // <-- Assigned here.
    3. }
    The variable primaryHand contains a reference to the actual item asset in your project. If you change it at runtime while playing in the editor, it will actually change the contents of this file in your project, and those changes will persist even after you stop playing. The safest thing to do is to instantiate a copy and change that:
    Code (csharp):
    1. public class Player : MonoBehaviour {
    2.     public Item primaryHand; // <-- Assigned here in editor.
    3.  
    4.     void Start() {
    5.         primaryHand = Instantiate(primaryHand) as Item; //<-- Make copy.
    6.         primaryHand.GetAttribute<CostAttribute>().cost = 0; //<-- Change the copy.
    7.     }
    8.  
    9. }
    You can still serialize this and save it in your saved games, but that's something you control manually in your own code versus Unity's automatic serialization when you're editing assets in the inspector. Hope that helps.
     
    Last edited: Jan 10, 2017
  34. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,032
    @TonyLi,

    Sort of. But looking at line 2 in the code immediately above, which item is primaryHand? How do we know (how have we specified) say it's the throwing dagger Item asset or the spatula Item?
     
  35. ung

    ung

    Joined:
    Feb 22, 2015
    Posts:
    154
    @Duffer123 I'm probably misunderstanding your question but here's my answer (please note that I'm not the poster of the above code)... The Item object itself should contain all the attributes needed to identify it. In @TonyLi example the Item object is assigned to player's primaryHand from the Editor (i.e., prior gameplay starts, at design time). That is just an example, of course, you will probably switch weapons at runtime. What makes a Dagger different from a Spatula? All weapon attributes (power, accuracy, etc.) are composed into it using ItemAttribute derived classes, what problem do you foresee in differentiating them?
     
  36. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,032
    I'm not fully understanding how simply describing the thing in primaryHand as Item ensures (and we can see) that it actually is the spatula or dagger. . I am probably missing the point?

    Also if I name the Assets created from Scriptableobject Item class how do I then search them by their name? Do I also have to add a public string itemName within the Item class as well?
     
  37. ung

    ung

    Joined:
    Feb 22, 2015
    Posts:
    154
    Item has a list of ItemAttributes. Item is just a container for attributes. Imagine item like an empty GameObject. An empty GO is nothing (well, it has a transform but just ignore it) but a container for MonoBehaviours. Each MonoBehaviour defines a little aspect of the GO (e.g., it's something renderable, it collides, etc.) so what a GO is it's actually defined by its components and not by the GO itself. So, an Item is like an empty GO and an ItemAttribute is like a MonoBehaviour.

    I prefer to have an ItemName variable as well. In my opinion the Asset should be named something like: "ShortSwordTemplate" while the item itself would be named "Short Sword". I would add ItemName to the Item class itself instead of creating an ItemAttribute sub-class (for ease of use). Look into the System.Collections.Generic.Dictionary type to sort collections by a given key.

    Code (csharp):
    1. // System.Collections.Generic.Dictionary
    2. Dictionary<string, Item> items;
    3.  
    4. // Create an item by cloning a ScriptableObject asset
    5. var item = Instantiate(ShortSwordTemplate) as Item;
    6. // Store it into the Dictionary
    7. items.Add(item.ItemName, item); // or items[item.ItemName] = item;
    8.  
    9. // Obtain a reference to the Item somewhere else
    10. Item item = items["Short Sword"];
     
    TonyLi likes this.
  38. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    9,524
    Every ScriptableObject has a name field. If it's saved as an asset, the name is the filename of the asset.
     
  39. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,032
    @TonyLi and @ung ,

    Thanks again for the info. It helps to clear it up in my mind. I like the idea of the dictionary index thing.

    My only concern is that Dictionaries do not serialise. Easy Save (which I own serialises them) but doesn't then serialise collections within collections etc within classes.
     
  40. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    9,524
    You could always serialize it yourself using ISerializationCallbackReceiver (see here), but it's probably easiest to simply use it as a runtime cache and not serialize it at all -- that is, if you need it at all. Don't get caught up in premature optimization.
     
  41. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,032
    Point taken... I'm still trialling the composition idea... ;)
     
  42. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,032
    @TonyLi ,

    Speaking of which (composition idea) .... I can't make it work.

    I can create multiple assets from (using above examples) the Item (scriptable object class) and I can make assets from (not the ItemAttribute scriptable object class, because it is abstract, but) things like the say DamageAttribute class.

    However, unfortunately, when I go to the say Sword.asset in the inspector I can drag in my DamageAttributeTemplate.asset but what I do not see is the public int damage variable within it say... I just drag in the asset as an unviewable .asset - that's it?

    I was hoping really that I'd only have to create one DamageAttribute asset and when I add it to each of my Item assets it allows me to enter for each Item asset different say values for Damage on each occasion?

    Actually the dropbox image below is in relation to a DescriptionAttribute - but within it it has a public string itemDescription...which I don't see in inspector for each Item.asset (allowing for different descriptions too.)

    What am I doing wrong? Please tell me I don't have to create a DescriptionAttribute.asset for each description I want to bolt in to each Item.asset as an attribute?!?... That would be completely untenable...


    Is this what I have to do (and in which case can you help me with some pseudo-code) if I want to create lots of item.assets without having to create the same amount of say damageAttribute (sub-class of itemAttribute) assets too...?
     
    Last edited: Dec 11, 2015
  43. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    9,524
    That's essentially what you'll do, but in practice you should create a simple custom inspector that lets you instantiate and edit subclasses of ItemAttribute. I'll be out of the office for several hours still, but I'll see if I can throw together a quick example when I get back.
     
  44. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,032
    @TonyLi ,

    Sorry about all these questions - but your assistance and guidance on this is really helping (and I can well imagine will help others in the community if they find this thread!).

    Any code along these lines you can feed me, gratefully received...

    I'm hoping a custom inspector is an effective workaround to creating 1000+ of each itemAttribute sub-class for the 1000++ item template assets.... ;)

    p.s. did you author Love/Hate (which I own, it's excellent!)?
     
    Last edited: Dec 11, 2015
    RemDust likes this.
  45. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    9,524
    It works well in the procedural quest generator I'm developing. It's how I assign custom attributes to quest entities. I think the example will make it pretty clear.

    Yes, thanks! :)
     
  46. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,032
    Consider my (hopefully this month!) purchase of the Dialogue for Unity asset payment in lieu then... ;)
     
  47. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    9,524
    Thanks again! Wait until the end of the month. I'm finishing up the next release of the Dialogue System and putting it on sale then.
     
  48. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    9,524
    Here's an example: http://pixelcrushers.com/misc/ItemExample_2015-12-11.unitypackage. It's in a folder "Item Example". There's a test scene, two items, and a Scripts folder. Sorry, this was just a quickie throw-together, so no comments except in the editor script.

    Item.cs:
    Code (csharp):
    1. using UnityEngine;
    2. using System;
    3. using System.Collections.Generic;
    4.  
    5. public class Item : ScriptableObject
    6. {
    7.     public List<ItemAttribute> attributes = new List<ItemAttribute>();
    8. }
    ItemAttribute.cs:
    Code (csharp):
    1. using UnityEngine;
    2. #if UNITY_EDITOR
    3. using UnityEditor;
    4. #endif
    5.  
    6. public class ItemAttribute : ScriptableObject
    7. {
    8.  
    9. #if UNITY_EDITOR
    10.     public virtual void DoLayout() { }
    11. #endif
    12.  
    13. }
    DamageAttribute.cs: (and similarly for other subclasses)
    Code (csharp):
    1. #if UNITY_EDITOR
    2. using UnityEditor;
    3. #endif
    4.  
    5. public class DamageAttribute : ItemAttribute
    6. {
    7.  
    8.     public float damage;
    9.  
    10. #if UNITY_EDITOR
    11.     public override void DoLayout()
    12.     {
    13.         damage = EditorGUILayout.FloatField("Damage", damage);
    14.     }
    15. #endif
    16. }
    And the big one, ItemEditor.cs:
    Code (csharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using System;
    4. using System.Reflection;
    5. using System.Linq;
    6.  
    7. [CustomEditor(typeof(Item))]
    8. public class ItemEditor : Editor
    9. {
    10.  
    11.     // Add a menu item to create Item ScriptableObjects:
    12.     [MenuItem("Assets/Create/Item")]
    13.     public static void CreateAsset()
    14.     {
    15.         ScriptableObjectUtility.CreateAsset<Item>();
    16.     }
    17.  
    18.     // Holds ItemAttribute types for popup:
    19.     private string[] m_attributeTypeNames = new string[0];
    20.     private int m_attributeTypeIndex = -1;
    21.  
    22.     private void OnEnable()
    23.     {
    24.         // Fill the popup list:
    25.         Type[] types = Assembly.GetAssembly(typeof(ItemAttribute)).GetTypes();
    26.         m_attributeTypeNames = (from Type type in types where type.IsSubclassOf(typeof(ItemAttribute)) select type.FullName).ToArray();
    27.     }
    28.  
    29.     public override void OnInspectorGUI()
    30.     {
    31.         var item = target as Item;
    32.  
    33.         // Draw attributes with a delete button below each one:
    34.         int indexToDelete = -1;
    35.         for (int i = 0; i < item.attributes.Count; i++)
    36.         {
    37.             EditorGUILayout.BeginVertical(EditorStyles.helpBox);
    38.             if (item.attributes[i] != null) item.attributes[i].DoLayout();
    39.             if (GUILayout.Button("Delete")) indexToDelete = i;
    40.             EditorGUILayout.EndVertical();
    41.         }
    42.         if (indexToDelete > -1) item.attributes.RemoveAt(indexToDelete);
    43.  
    44.         // Draw a popup and button to add a new attribute:
    45.         EditorGUILayout.BeginHorizontal();
    46.         m_attributeTypeIndex = EditorGUILayout.Popup(m_attributeTypeIndex, m_attributeTypeNames);
    47.         if (GUILayout.Button("Add"))
    48.         {
    49.             // A little tricky because we need to record it in the asset database, too:
    50.             var newAttribute = CreateInstance(m_attributeTypeNames[m_attributeTypeIndex]) as ItemAttribute;
    51.             newAttribute.hideFlags = HideFlags.HideInHierarchy;
    52.             AssetDatabase.AddObjectToAsset(newAttribute, item);
    53.             AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(newAttribute));
    54.             AssetDatabase.SaveAssets();
    55.             AssetDatabase.Refresh();
    56.             item.attributes.Add(newAttribute);
    57.         }
    58.         EditorGUILayout.EndHorizontal();
    59.  
    60.         if (GUI.changed) EditorUtility.SetDirty(item);
    61.     }
    62.  
    63. }
     
    phobos2077, laurentlavigne and xNex like this.
  49. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,032
    @TonyLi,

    Excellent.! . Thanks again. . I'll give this a go in the morning.

    I've got the code above but for some reason the unity package won't download?

    Does the damageAttribute .cs etc go in an editor folder as it mentions using UnityEditor?

    [edit] Also, how would this work if one of the itemAttribute sub-classes contained a collection? or enum?

    [edit] It's probably best I import your package (if you could get that link to work) as I must be doing something wrong with code above (maybe I had to put damage or item attribute classes in to an editor folder?) because when I generate a new item asset and click on it's there's nothing in it! No option for editable attributes etc.

    [edit] I've now managed to download your package example and THAT works... hmmm

    [edit] OK, given it a try. The first thing that occurs is that if I'm going to have LOTS of attributes to a very catholic selection of LOTS of items, the overheads in terms of time in setting up the editor / doLayout stuff are going to be a total killer...?
     
    Last edited: Dec 12, 2015
  50. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    9,524
    I actually do it a little differently in my quest generator. Attributes define data types, such as float or string. (Technically in the quest generator they're data providers -- they can also represent enemy counts, faction relationships, current weather values, etc.).

    So rather than writing separate classes for DamageAttribute, CostAttribute, and ArmorAttribute, I just have a single FloatAttribute that has some extra information describing the purpose of the float value. Using this approach, I'd just add three FloatAttributes and set them up for damage, cost, and armor.