How to manage different types of item?

Hello everyone,

I am creating an inventory system and an equipment system and I was wondering how you usually manage the different types of items.

Here is my code:

public class ItemData
{
    [SerializeField] bool isStackable;
    [SerializeField] float size;
    [SerializeField] float weight;
}

public class Item
{
    [SerializeField] ItemData data;

    public ItemData Data => data;
}

public class ItemSlot
{
    [SerializeField] Item item;
    [SerializeField] int amount;
}

I have an item data that stores the information about an item, an item class that uses and manipulates that data, and an item slot that stores the amount on a given item.

Now I want to create a sword. The way I see this is to create a sword data class derived from the item data class and the sword class would derive from the item class.

public class SwordData : ItemData
{
    [SerializeField] float damage;
}

public class Sword : Item
{
    public SwordData SwordData => Data as SwordData;
}

Is this a good implementation for you?

I found that inheritance does not make a good item system. And, I think we’ve gone through the failings of inheritance with you ad-nauseum.

For one, you can’t mix and match item properties as you can’t inherit from multiple classes. Secondly, you’re going to have to hard-code stuff to look for a sword, which is just going to get cumbersome before long. You could abstract this out to the concept of a ‘weapon’ as opposed to a sword, though you’ll still need some way to define the different kinds of weapons.

You want, as we’ve mentioned before, composition. Ergo using interfaces, or having ways to mix and match information like Unity’s component structure.

For example, using interface:

public interface IWeapon
{
    WeaponProperties Properties { get; }
}

[System.Serializable]
public class WeaponProperties
{
    [SerializeField]
    private WeaponType _type;
  
    [SerializeField]
    private float _damage;
  
    public WeaponType Type => _type;
  
    public float Damage => _damage;
}

[CreateAssetMenu]
public class WeaponType : ScriptableObject
{
    [SerializeField]
    private string _name;
  
    public string Name => _name;
  
    // other weapon type properties
}

[System.Serializable]
public class Weapon : Item, IWeapon
{
    [SerializeField]
    private WeaponProperties _properties = new();
  
    public WeaponProperties Properties => _properties;
}

This is following your rough existing structure, though nixing the ItemData system as I feel that’s someone superfluous. And this still gives you the room to make other items that can also be weapons.

Though the most flexible system I’ve found is a component-like system akin to Unity’s component, but using plain C# classes and [SerializeReference], and using a Get/TryGetComponent to test whether items express certain components.

Nah we don’t actually use inheritance unless it’s some kind of a fairly rigid system design with lots of abstractions and moving parts, which is sometimes important for the overall extensibility of the project.

For the top-level game stuff, I believe all of us use interfaces (for categorization as well as for usage ergonomics) and ScriptableObjects (for the data itself).

Of course there are exceptions to everything, but there is really no need to underpin all your objects with some vague abstractions that serve no practical purpose, you’ll only choke yourself later with base classes and wish you didn’t use inheritance at all.

I’m sure others will fill you in with more details.

Edit: and voila, spiney was quicker, and you can see that his set up does exactly what I said.

Thank you for the reply and this example, it really helps.

Yes, I remember discussing this with you. I know that inheritance is not used in this context and I wanted to ask this in a dedicated thread.

Why the type is not in the property class instead of its own class? Like so?

public interface IWeapon
{
    WeaponProperties Properties { get; }
}

[CreateAssetMenu]
public class WeaponProperties : ScriptableObject
{
    [SerializeField] string _name;
    [SerializeField] float damage;

    public string Name => _name;
    public float Damage => damage;
}

[System.Serializable]
public class Weapon : Item, IWeapon
{
    [SerializeField] WeaponProperties properties;

    public WeaponProperties Properties => properties;
}

public class Item
{
    [SerializeField] ItemData data;

    public ItemData Data => data;
}

EDIT: Does it mean that I should do the same for a simple Item? I mean, creating a interface like so:

public interface IItem
{
    ItemData ItemData { get; }
}

public class Item : IItem
{
    [SerializeField] ItemData data;

    public ItemData ItemData => data;
}

Or it is unnecessary?

It was half the point of my composition example! It’s a scriptable object, so you can make any number of scriptable objects to define different weapon types. And then when you want to check if a weapon is a certain type, you can compare it with Unity object equality. Eg: (someItem.Properties.Type == someWeaponType). And if you need any information specific to the type of the weapon (and not the weapon itself), it’s all there in the scriptable object in a completely modular fashion.

The Name property was the name of the type of weapon, not the weapon itself.

To me, the SO stored the type, the base damage and all that stuff and the weapon class is the one that modifies the base value with buffs or debuffs, right?

public interface IWeapon
{
    WeaponProperties Properties { get; }
}

[CreateAssetMenu]
public class WeaponProperties : ScriptableObject
{
    [SerializeField] string _name;
    [SerializeField] float _baseDamage;

    public string Name => _name;
    public float BaseDamage => _baseDamage;
}

[System.Serializable]
public class Weapon : Item, IWeapon
{
    [SerializeField] WeaponProperties _properties = new();

    public WeaponProperties Properties => _properties;

    public float Damage => _properties.BaseDamage + buff;

    float buff;
}

That’s not at all relevant to what I was trying to get across.

You asked how to design different types of items. I showed you how to define different types of weapons. A type of weapon would express the type’s name, perhaps an icon for the type, the requirements for using the type (think DnD simple, martial, exotic weapons), etc. These would be static, fundamental properties across all weapons of that type.

The weapon itself would then express its damage and other per-item data.

So what is wrong with this:

public interface IWeapon
{
    WeaponProperties Properties { get; }
}

[CreateAssetMenu]
public class WeaponProperties : ScriptableObject
{
    [SerializeField] string _name;
    [SerializeField] float _baseDamage;
    public string Name => _name;
    public float BaseDamage => _baseDamage;
}

[System.Serializable]
public class Weapon : Item, IWeapon
{
    [SerializeField] WeaponProperties _properties = new();
    public WeaponProperties Properties => _properties;
    public float Damage => _properties.BaseDamage + buff;
    float buff;
}

There is a property/data that is static with its name and its base damage, and a weapon class that expresses damage per item data.

You want to keep that WeaponType class to encapsulate all the swords, hammers, bows, etc? Is that why you have this class?

I mean I’m not sure why WeaponProperties needs to define the name. The name of the weapon should just be the name of the item. Otherwise I don’t see any surface-level issues with it.

Yes, that’s what I mean by types of weapons.

I am wondering, what about a pickup system. In the case you wrote, it means that I have to make a Pickup class for each type of item, right? One for a weapon, a wand, a bow etc. Is it something that we want or it is better to have only one pickup class for any item?

I’m not sure how you would come to that conclusion. Nothing about what I’ve outlined would require this.

It’s important to remember items in games are just data. An item pick-up in the game world is just a representation of that data. In Unity’s case, it usually is a game object with a component that points to the item that it represents, and pulls from this data to build its visual representation (usually a prefab to spawn in the world, but can also grab a mesh to assign to a mesh-render, for example).

This doesn’t require an special implementation per item type. It’s something that can be applied to all items as it happens on a layer above.

I came to this conclusion because what defines an weapon is the weapon properties class which is a C# class so to define the damage of your weapon, you need to call it somewhere since it is not stored in the scriptable object.

With a PickUpItem class like so, the only thing you can refer to is the WeaponType, but you don’t know anything about the damage inside. So where do you define this weapon?

If you’re working with pure data items, then you need to devise a system to loosely reference them. Such as through a uniquely generated ID, or one you manually assign. Then your pickups can use this ‘loose reference’ to build their representation.

At runtime an item instance can be generated and tied to a given pickup. At design-time you can potentially generate an instance via editor tooling.

Again, nothing that needs a specific implementation per type of item. It can all work universally.

With the example you wrote, I really don’t see how. Since it is a mix of pure data item where the properties define the damage and the weapon type which is the type the weapon is part of.

If I want one script to pickup any item, I really don’t see how to make it.

public interface IWeapon
{
    public WeaponProperties Properties { get; }
}

[System.Serializable]
public class WeaponProperties
{
    [SerializeField] WeaponType _type;
    [SerializeField] float _damage;

    public WeaponType Type => _type;
    public float Damage => _damage;
}

[CreateAssetMenu]
public class WeaponType : ItemData
{
    [SerializeField] string _name;

    public string Name => _name;
}

public class ItemData : ScriptableObject
{
    [SerializeField] Sprite icon;
    [SerializeField] GameObject prefab;

    public Sprite Icon => icon;
    public GameObject Prefab => prefab;
}

[System.Serializable]
public class Weapon : Item, IWeapon
{
    [SerializeField] WeaponProperties _properties = new();

    public WeaponProperties Properties => _properties;
}

public class Item
{
    [SerializeField] ItemData itemData;

    public ItemData ItemData => itemData;
}

public class PickUpItem
{
    [SerializeField] Item item;
    [SerializeField] int amont = 1;
}

This won’t work since an Item is just an item and cannot change to a weapon or anything else.

Does the WeaponType derives from the ItemData which contains the prefab and the icon

It would help if you didn’t just ignore half of what I wrote. Particularly the part about loose references.

Like I said, a pick up would not have a direct reference to an item, or have the item serialised directly into it. It would have loose or indirect reference, such as only serialising the item’s unique ID. I’m just repeating myself here.

It’s the kind of stuff where you need to design a whole supporting system/API to make work, which will often include editor tooling. It’s not going to happen in a few scripts and can’t really be shown in a forum post as we’re talking hundreds if not thousands of lines of code.

And FWIW, you could do this:

public class PickUpItem
{
    [SerializeReference]
    private Item item = null;
  
    [SerializeField]
    private int amont = 1;
}

And with some editor tooling, select derived types of Item, and thus, a pickup works with every item. But there’s better ways to go about that. A pickup shouldn’t have a direct reference to the item.

I did not ignore the half what you wrote only the fact that if I use scriptable objects, it is not to create an ID base system with ID references. Otherwise, what is the point of scriptable objects, weapon type etc.

But what you could also do is not to forget the half of the explanation and don’t think that is trivial cause it is to you but not for the others. So yes, I did not know that SerializeReference exist and not even how to use it. I knew that interfaces are very useful but I did not understand how to implement them for a item system.

Also if you are tired of explaining to me or anyone else, you could just stop. I am not here to read someone complaining about explaining something but instead learn what I did see in any videos yet or articles.

As you said one day, you started code in 2021 and now you explain to me how to do things even if I migh started earlier. But I don’t care of that, the only thing that matter to me is to improme myself so yes I ask question if I have and ask again if I still don’t understand. Now, you also can give the resources you used to reach that skills and I will leave you with my questions.

I’ve read through this and the previous thread without commenting until now and I feel like the problem amounts to “you just need to make games”. You’ll quickly run into what is good and what just sucks. Most of this feels like too much unnecessary complexity.

Have you heard about the developer of Stardew Valley? He developed his game on his own without looking for solutions to his problems and tried to solve them himself. The more I think about this story, the more it seems to be a good solution, even though at first I thought it was a very nice way to waste his time.

Without going to that extreme, I am going to do that, make my game and learn along the way.

Tim Cain (lead programmer of the original Fallout) has a good video on a combat system but the best part isn’t the system itself but rather how he arrived at it: he kept trying new combat systems with every game he made until he arrived at one that he liked.

If you want to worry about anything worry about how to store the data so that you don’t have to retype everything if you decide that you want to change something later. ScriptableObjects aren’t always the easiest things to work with even with custom editors.

I’m going to say, aside from games with very basic inventory/item systems with no save game requirements, you are going to find it very difficult to make an inventory system without using some kind of ID look-up system to back it.

I’d advise to think about ahead about how you’re going to manage your save games. They will heavily influence your runtime data, if not be the primary guiding factor in how your runtime data works.

Also agreed. Every solution and system I’ve devised has just been through plugging away and working on my projects. Lots of iteration, bit of experimenting, and honestly, a lot of creativity. I think folks forget programming is as a creative endeavour as any other art discipline.

Like I said above these ideas come from my personal experience. I couldn’t point to any specific resources as most of these I devised on my own, or were inspired by things other folks have done.

And it’s worth remembering these forums are about pointing you in the right direction, particularly when it comes to broad architectural questions like these. We can give you ideas and input from what has worked for us in the past, but every project is different and ultimately it comes down to the individual implementation details.

One thing to accept is you will code yourself into a dead-end and have to roll back that work. But so long as you learn from it, and remember to account for it in future, it isn’t time truly wasted.