Hello, I have implemented an item system, but after looking at other examples, I feel like my item system is somehow flawed. I was wondering if someone can give some input.
So, here is how it works. There is a list of ItemData instances which is the ItemLibrary. This Class contains data of the item which is id, name, description, GameLogic, and Assets (Sprite, Mesh, Material).
GameLogic is a Type that derives from BaseItem. There are many Types Deriving from BaseItem like ClothingItem, ConsumableItem, etc.
Each item has itâs own class that derives from one of these items, for example, âBananaâ derives from ConsumableItem while âJacketâ derives from ClothingItem.
Now using the Item Class mentioned at the start, we put it into a SpawnItem method that adds the GameLogic Type as a component to the newly created GameObject.
I would appreciate anyoneâs feedback. Thanks in advance!
Are your ItemDataâs scriptable objects? Almost sounds like from your description that theyâre just plain classes.
Nonetheless you will find that with inheritance structures that youâll have issues having something be more than one thing. What if you want something that is both wearable and consumable?
I fell into that pitfall working on my own item system. Had a base item scriptable object class, and would derive from it to make different types of items (craftable items, refinable items, castable items (ie, cast from metal)) by use of Interfaces. But when I needed to make an item that was multiple things, the amount derived classes got silly, eg, RefinableCastable, RefinableCastableCraftable⌠you get the idea.
Switching to a component structure proved to be the solution, when I learned that OdinInspector could expose SerializeReference. Thus I just have the one item class, that have a list of components in them instead to define all their behaviour.
In your case, the simplest solution could be to have a List instead of a single field, so as to define multiple behaviours, though youâd have to ensure they donât conflict with one another.
Though really the item system depends heavily on the game. The game Iâm working on is all about items and crafting, so my solutions are heavily engineered but would be totally unnecessary in a game with a much smaller focus on said mechanics.
Hello, first of all, thanks for your detailed response.
No, I try to stay away from scriptable objects, they look confusing to me.
As for your response, I have seen a lot of posts on the unity forums and on stack overflow which recommend multiple behaviors so I might have to try this out. But I do feel like that might get complicated real fast.
I feel stuck because my current method allows for extremely customizable item behaviors , but as a con, even if they are very similar, they each have their own class. Apple and Banana consumables do the same thing for example. The only difference between them is their assets and the amount of hunger they fill. Yet they both take separate behaviors. This is probably what is bothering me most, because it doesnât feel efficient in the long run to have like 20 or so consumables behaviors for that amount of consumables. Do you think that this might be a problem, or am I just worrying too much?
Also If you donât mind me asking, how do you separate your item data from your item logic, if you do so? At first I thought I could just store the items ids in a text file for persistency, but for items with durability, special states, and etc. an item id wont do the trick.
Youâre missing out man! Theyâre your best friend when implementing these sorts of systems. As sources of immutable data theyâre perfect and I highly recommend you play around with them.
I admit Iâm still trying to get my head around how you organise your items. So is ItemData a monobehaviour then? Are you making lots of prefabs with various components attached to them? Or are they plain C# classes stored in a big list somewhere? Some examples of your code wonât hurt.
And by separate behaviours, do you mean multiple instances of a monobehaviour component on a game object(s), or multiple different scripts that do more or less the same thing? Thereâs nothing wrong with the former, but definitely something wrong with the latter. Ideally for a basic consumable you shouldnât have to write more than one script (that changes when you have more complicated forms of consumables).
More than happy to divulge in my methods! So Iâll preface this by saying I heavily use OdinInspector to make what I do possible, and again as my game heavily revolves around items it gets a bit complicated perhaps. But the core structure is a but like this:
ItemBase being a scriptable object with all the immutable data and components. The item itself just outlines the core data, and the list of components each define what can be done with that item, alongside the data that coincides with that purpose. As of yet I donât have any items that define any particular game logic themselves, thatâs inferred depending on whether or not an item has a certain component. But if I did I would be utilising SerialiseReference for such a purpose.
Each item scriptable object in its core data has a unique GUID as well, more on that later.
ItemBaseWrapper is a plain C# wrapper class that stores a reference to an item, and has a list of meta-components that define unique or mutable data (such as name changes, durability, etc), as a way to have data that doesnât affect the source SO. This is the data thatâs written to a save file.
The complication that occurs with this, is that because all the meta components are class type objects, I have to overload the == and != operator when comparing two ItemBaseWrapperâs, alongside for each meta-component I add as well.
And as I donât have a grid style inventory, ItemWrapperListing is basically another plain C# wrapper class which contains an ItemBaseWrapper, and a quantity of said item, and lists of this class are used in my inventory scriptable object, or wherever I need an item and a quantity associated with it.
For saving, both ItemBaseWrapper and ItemWrapperListing (and the inventory scriptable object) have serialisation surrogates, the former of which saves its item by storing the itemâs GUID and all the meta components of the normal wrapper itâs copying. When loading, the item is loaded via itâs GUID from a database, and the meta components are all reconstructed from the data written to file.
Of course not every game needs a system this involved. If items are a lighter part of your game, you could, for example, just have prefabs that you mix and match monobehaviour components (though this probably wouldnât work for storing items in an inventory).
Woah, sorry for keeping you waiting so long. We must be in different time zones. I was sleeping when you posted this.
Since my game is going to be a bit more procedural and customizable, I try not to use that much Immutable data that much. As for Prefabs, I donât like using those either . I donât know why but I feel like things will get messy real fast if I use Prefabs in my game. My plan is to initialize the assets at the start of the game, that come from a file. (I probably wont be using the Unity Resource file for this)
Examples? No problem! I will give a simple example:
The data of each item is stored in a class called Item
public class Item
{
private string id;
private string name;
private string description;
private Type gameLogic; // This is the Item Behavior
private bool inInventory;
private Vector3 position;
private Vector3 rotation;
private Mesh worldMesh;
private Material worldTexture;
private Sprite inventorySprite;
}
The Item data is stored in the BaseItem Behavior, The base methods that are in here are used by all Item Behavior
public class BaseItem : MonoBehaviour
{
public Item itemData;
public void InventoryToWorld();
public void WorldToInventory();
}
Finally we have the Item (aka GameLogic) that will Inherit from BaseItem, or one of its derivations, in this example I will show an item called Ramp (just an example, couldnât think of anything else)
public class Ramp : BaseItem, IItem
{
private string _name = "Ramp";
private string _description = "A small ramp that you can walk on";
public string description { get => _description; set => _description = value; }
public string itemName { get => _name; set => _name = value; }
public void OnClick()
{
Debug.Log($"You have clicked a {_name}");
}
}
Ignore the IItem interface, I will probably get rid of it soon.
Then I have a method called CreateItem() that takes in the itemdata, a bool to decide if it is in the inventory or not, a position and a rotation.
Same, I wonder how similar are item systems are? I just felt tired with every game using a grid inventory, and wanted to make something a little unique.
Iâm doing the former I guess I should change my system but its going to be a pain to do so.
Iâll be honest, from the general description you gave, this is going to save you dramatically more work in the long term than trying to keep the current system manageable and extensible. To build off the idea of a component based system, you can actually integrate this sort of thing into a scriptableobject setup as well. Unity has a very decent guide for doing something like this that focuses more on AI, but it can easily drive an inventory system where you can add components to items as needed.
Yeah from what I can see youâre going to have a hard time keeping this expandable or even maintainable. I agree you get some form of flexibility by being able quickly populate new items by using your monobehaviour component, but having to hard/hand write every item like that is also going to bloat your code base and leave you with lots of repeated code (remember DRY!).
Definitely take a look at scriptable objects. You should in theory be able to just have the one item scriptable object class and use a component structure to add all the functionality you need. Then a wrapper class can be used to add modified data. Having items as assets as being able to drag them into object fields is also a huge benefit over monobehaviours.
As mentioned above you could do a Unity default component structure with scriptable objects, though I find that unwieldy. If you are keen on making a deep item system, I would also say itâs worth investing in an inspector plugin like Odin Inspector. Youâll be saving yourself months of time and headache.
Donât worry, weâve all been there. I had a different item system before as well, and had to make the same decision you had. My advice is to make the new system in parallel, rather than trying to change all your existing code. That way you donât have to deal with each change breaking everything else, and once youâve built up the code of the new system and itâs all integrated, you just have to ensure the old system doesnât have threads anywhere before retiring it for good.
My inventories are just a list. Iâll post a screenshot of my prototype UI:
Took this when I was just finishing up with my meta data system. Both Stone and Dense Stone are the same core item scriptable object, just Dense Stone has some meta data components changing itâs name, weight and volume. (Also Iâve literally just noticed the units arenât being converted in the summary on the right, gotta fix that).
Donât worry mate, item systems are one of those things that look real fun to code but turn out to be a complicated beast. All part of the learning adventure!
WOW, thatâs one heavy rock! Yet its worth so little
Thanks for your help, @spiney199 , I will change my item system to use one behavior for multiple alike items instead of my current system where I use a separate behavior for each item. I will also look into a component system as well. Just do be clear though are you saying that I have one monobehavior and in that, a list of components? Or just multiple components in the Gameobject?
i have heard of OdinInspector and it looks pretty cool. OdinSerializer looks pretty cool as well. By any chance have you tried it? I was just going to use json.net
Also Thanks for the link, @Murgilod , Iâll check it out soon!
Honestly Iâm saying you should use scriptable objects. I think if you continue using monobehaviours and game objects for game items, youâll very quickly hit complications you canât easily overcome. You can of course have items in the world, but they should just be game objects that reflect the data of a scriptable object theyâre holding a reference to.
Odin was the first plugin I bought when I started using Unity and it is by far the best addon I have (with PEEK just behind).
For example, this is what the inspector for my items looks like:
And not a single property drawer to do so! The serialiser is also very handy as youâll 100% come across a data type Unity canât serialise, and working around it is usually an epic on its own. Not that you should abuse it as it does have an overhead compared to just letting Unity serialise its assets.
As far as using the serialiser to output data like save files, I havenât used that yet but itâs on my list of things to try out, as itâs meant to be a lot faster and performant than serialisers like Newtonsoft.JSON.
Absolutely yes. Any form of item or object system will always haunt programmers. I havenât looked at your code, but this is just something that will always haunt programmers. Maybe thatâs worth something.
At the moment youâre working towards making your life more difficult by fighting the framework.
Problems.
Youâre trying to avoid scriptable objects in situations where theyâre really useful.
Youâre trying to avoid use of prefabs in situations where theyâre useful.
Youâre using inheritance to implement item types.
Youâre mixing naked C# classes with unity types such as material.
Youâre using references to Materials and Meshes instead of prefabs.
Thereâs no good reason to make Jacket a class. Instead âJacketâ should be an instance of an âItemConfigurationâ which would be able to describe any item in the game. The âItemConfigurationâ should be implemneted as a ScriptableObject.
It might help to think âhow would I store item configurations in a databaseâ. Database does not really have inheritance, and stores items in uniform fashion. Then use that.
Youâre trying to avoid scriptable objects in situations where theyâre really useful.
Iâm still a little confused on how they will help me. So far I have a class that contains the inventory sprite, the mesh, and the material. That class is put into a list and through the unity inspector I add those references. When I need to use these assets I just reference them when creating the list of items.
Youâre trying to avoid use of prefabs in situations where theyâre useful.
I Donât really see how prefabs will help me. It seems time consuming to create a prefab for every item instead of having a list of item data and creating a game object at runtime.
Youâre using inheritance to implement item types.
I agree that my item types shouldnât really be done the way they are. I will change it so that there is one Behavior for common item types.
Youâre mixing naked C# classes with unity types such as material.
Is there any cons of doing this? I never saw any issues doing it this way, but I also donât know much
Youâre using references to Materials and Meshes instead of prefabs.
The thing is, I use the same Gameobject for when the item is in the inventory and out of the inventory. For example if an item falls out of your inventory, I take off the sprite render, and 2d related stuff, and add a mesh renderer, mesh collider, and etc. Then I change its position to be in front of the player.
Sorry if my answerâs donât make any sense. Im really confused now what to do. Do I use composition for my items?
Scriptable object is a datablock that sits in the project, can be referenced from components, and automatically receives inspector support. So if you make an âItemConfigâ derive from scriptable object, then in any component declare public ItenConfig itemConfig;, that will create an field in inspector window, where youâll be able to assign itemConfgig you want, click and select fomr those available in the project and so on. Youâll be able to organize them into folders, duplicate, etc. Easily.
One of your classes had fields âMeshâ, âMaterialâ and so on. That class screams that youâre trying to reimplement prefab and are thinking that those parameters are sufficient. Theyâre most likely are not sufficient, and the moment you require a particle system attached on some objects and animation on some objects, your system will go caput.
Prefab can be anything. It can be sprite, 3d model, it can have sound or animation attached, all that without changing the class. And you WILL have to implement visual representation of every item anyway.
Youâre missing Inspector support. Scriptable objects automatically implement serialization in the project. If you create a ScriptableObject with a âMeshâ, âMaterialâ or âGameObjectâ field and drag whatever you need into it, itâll will be automatically serialized to disc when you edit it. In your case youâll need to reimplement serialization yourself, which is going to be a huge waste of time.
This is overcomplicated.
You can store two prefab references in item config. âworldObjectPrefabâ and âinventoryScreenPrefabâ. In both of those you can attach a single component which will reference âItemConfigâ related to it. The reference can be set automatically on spawn. When you need to spawn the visual in the world or inventory, you instantiate the prefab and set itemconfig reference.
I would advise to ditch current system and implement a version using prefabs and scriptable objects.
Prefab/scriptable object implementation can be done VERY quickly. In a few evenings, it will be done.
The system youâre developing now, you can easily waste a month on it. And youâll gain nothing in return.
Among the programming principles one I find useful is âKeep It Simpleâ. Right now youâre going against this principle and develop your own framework from scratch, instead of using what engine provides. By doing that youâre being sidetracked from finishing your game.
Sure! Itâs a list of a base abstract class, ItemComponentBase, serialised with SerialiseReference. OdinInspector is able to expose these values, so you get polymorphic lists using default Unity serialisation.
All it really is is this:
[SerializeReference]
private List<ItemComponentBase> itemComponents = new List<ItemComponentBase>();
As for using them, you can just write your own simple âGetComponentâ and âTryGetComponentâ equivalents. Ergo, when if (TryGetItemComponent(out ItemComponentSellable sellableComponent)) gets a hit, you know the item is sellable, and you have a reference to that component as well.
And whenever you need to add new functionality to an item, you just make a new deriving item component class.
Because inventory data doesnât need Transform in most cases and doesnât need to exist as instanced GameObjects unless the items are actually pickable in the scene. i.e. Scriptable Objects have a smaller footprint which is what you want from your data.
Scriptable Objects also have [CreateAssetMenu] attribute so you can easily create new scriptable object instances via right click context menu. And since Scriptable Object calls Awake() when its created, you can do some configuration automatically, like generating GUID for the item if ID string isNullOrEmpty and other related setup.
Itâs also easier to deal with Scriptable Object assets which are unique rather than GO instances in Inventory system context. Itâs easier to populate an item database automatically if you have a scriptable object type you can look for, instead of filtering all GameObject prefabs in the project or trying to limit them to a singular folder. Itâs safer and easier with Scriptable Object. And you likely can save a GetComponent call or two since youâre operating with Item scriptable object type rather than regular GameObjects.
In short, Scriptable Objects have smaller memory footprint, are literally made for a use case like an Inventory system with things like right click Create context menu, have lifecycle calls for automatic configuration upon creation, and are generally easier to handle and manage as project grows, thereâs almost no room for human error when creating a Scriptable Object instance in comparison to a prefab.
If youâre talking about the component list, it can live both in a scriptable object and Monobehaviour. If Item is modifiable at runtime, youâd have it in both the item SO that defines the base behaviour and the ItemWrapperMonobehaviour for runtime changes.
As above, you genuinely donât want to use game objects for inventory items. Itâs adds tons of unnecessary burden, as half your code will be dealing with transforms when it genuinely doesnât need to.
If anything a good inventory system should have as little to do with Unity in general. This is one of the things that you can code almost exclusively in the plain C# world, as inventories are just data. Scriptable objects often make the cut as they provide a useful way to author data in Unityland.
Problem with plain classes, it makes it hard to author things in Unity. You canât really drag and drop plain classes into loot tables or crafting recipes, but you can easily with scriptable objects.
You can implement multiple interfaces on scriptable object types, though this let to a lot of boilerplate, which is why I ended up with the component method.