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?
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…)
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:
If your character has a MonoBehaviour with a list of Items:
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:
public class Inventory : MonoBehaviour {
public List<Item> items;
}
public class Item : ScriptableObject {
public List<ItemAttribute> attributes;
}
public abstract class ItemAttribute : ScriptableObject {
}
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.
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?
It’s abstract because it’s an empty stub. The idea is that you’ll make subclasses such as DamageAttribute:
public class DamageAttribute : ItemAttribute {
public float minDamage;
public float maxDamage;
}
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:
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.
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…
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…
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.
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.
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.
/// <summary>
/// Base class for item attributes.
/// </summary>
public class ItemAttribute
{
// ...
}
/// <summary>
/// An example attribute storing the "Attack Power" of a weapon.
/// </summary>
public class WeaponAttribute : ItemAttribute
{
public float Power;
}
/// <summary>
/// An Item is a simple collection of attributes.
/// </summary>
public class Item
{
private IDictionary<Type, ItemAttribute> m_Attributes;
public Item()
{
m_Attributes = new Dictionary<Type, ItemAttribute>();
}
public void Add(ItemAttribute attribute)
{
m_Attributes[attribute.GetType()] = attribute;
}
public TAttribute GetAttribute<TAttribute>() where TAttribute : ItemAttribute
{
ItemAttribute attribute;
if (m_Attributes.TryGetValue(typeof(TAttribute), out attribute))
{
return (TAttribute)attribute;
}
return null;
}
}
/// <summary>
/// A Database storing all items in our game.
/// </summary>
public class ItemDatabase
{
public IList<Item> Items { get; private set; }
public ItemDatabase()
{
Items = new List<Item>();
}
public Item FindWithAttributeValue<TAttribute, TValue>(Func<TAttribute, TValue> valueGetter, TValue value)
where TAttribute : ItemAttribute
{
return Items.FirstOrDefault(item =>
{
var attribute = item.GetAttribute<TAttribute>();
if (attribute != null)
{
return value.Equals(valueGetter((TAttribute)attribute));
}
return false;
});
}
}
public class MyScript : MonoBehaviour
{
public ItemDatabase ItemDatabase;
public void UsageExample()
{
// Finds an Item having a WeaponAttribute with a Power value of 5.5f
var weapon = ItemDatabase.FindWithAttributeValue<WeaponAttribute, float>(
attribute => attribute.Power, 5.5f);
}
}