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. Dismiss Notice

Question Inventory/Item system

Discussion in 'Scripting' started by Davicro, Jun 26, 2023.

  1. Davicro

    Davicro

    Joined:
    Nov 28, 2018
    Posts:
    15
    So i've been trying for a while now to figure out a good item system for my game. I thought I found a solution by using GameObjects as items, since it checked all of my requirements:
    • The item needs to be visible when equipped (I just position the GameObject to the correct slot)
    • The item needs to be an instance, that is, have unique values such as durability and other fields specific to certain items
    • It needs to have a pickup with proper colliders. That is, the colliders should match the shape of the item.
    • Weapons will have trigger colliders that will define the hitbox of that weapon. For example, a sword would have a box collider covering the entirety of the blade.
    • Some items will have unique animations.
    • Items will be usable, and even have unique behaviours while equipped. (Such as balloons lowering your gravity)
    The way I'm doing it now, is I have a
    public class Item : MonoBehaviour, IInteractable
    attached to a GameObject, where I define the behaviour of the item, and then a
    public class ItemData : ScriptableObject
    where I keep the name, description, stats, etc. This works fine, and allows me to easily make items with unique behaviours, animations etc, since I'm just working with a normal GameObject, and it fulfills every requirement on the list.

    But, I don't like this approach since it creates a lot of problems when handling it in the inventory. Here are a few examples:
    • When picking up the item, I need to disable all colliders and rigidbody, and when dropped re-enable them. This just feels very hacky.
    • Every slot has two fields: Item and Amount. When I have, let's say 10 of an Item in a slot, there is really only one instance of that item. But when I need to split it into multiple slots, I need to create a new instance for every slot, and when put back together, I need to destroy any extra instances. Doing this often could lead to performance issues I think.
    • Since the item is both the pickup and the item, I can't have an item pickup that gives you x amount of that item.
    • Using prefabs to store items is a bit of a pain

    Ideally I'd want to have ItemData scriptable object and Item class to define behaviour. Then have some sort of visual representation of that object in the world. But then I wouldn't have two of my requirements: Animations, and the colliders.

    I've searched and searched but I can't find anything that suits my needs, which leads me to believe maybe I should rethink what my necessities should be. But I still wouldn't really know what the best way of doing an inventory system would be.

    Can someone smarter/with more experience than me guide me a bit on what I should be doing? Should I keep using GameObjects as items, should I use normal classes? Or should I rethink everything?
     
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    I think you're forgetting scriptable objects can reference prefabs. Problem solved. The prefab has all the colliders, etc, and can just be instanced from the reference to the scriptable object.

    In any case, using game objects for inventories is not a good idea, as you're right to think. Games like Subnautica went with that path and they were worse off because of it (though the code production of that game seemed to have been mostly out-sourced). An inventory can effectively live in the plain C# world, though scriptable objects are useful for authoring data, such as for the items themselves, inventory objects, etc.

    Unique values are handled by having a class that wraps around the item SO itself, and acts as an intermediary. Most if not all 'gameplay' use of items should be through this instance wrapper.

    I highly recommend reading up on
    [SerializeReference]
    and tools that let you make use of it (such as Odin Inspector). Being able to serialised plain C# classes with polymorphism is invaluable, and will save you from having a messy inheritance heirarchy or tons of boilerplate with interfaces. Eg, you can often work with just one item class, and use SerializeReference to outline 'item components' that are serialised into a collection, that define all the behaviour of an item.

    Also make sure you think about how to save and load these earlier rather than latter. In any case, make a test project and play around with ideas on a small scale. This kinda stuff is going to put you through the wringer with C# more so than Unity, so expect to revise and refactor a lot.
     
    MaskedMouse likes this.
  3. Davicro

    Davicro

    Joined:
    Nov 28, 2018
    Posts:
    15
    I see, so the structure would look something like this:
    ScriptableObject for data
    Class for behaviour
    Prefab for visual respresentation

    So when I equip the item into visible slot, I instantiate the prefab. But I'm still seeing a few problems:
    • The pickup has physics, while it shouldn't in the inventory. Which means I either have two prefabs: one for the pickup, and one for the item, or I disable the physics for when it's in the inventory.
    • How would I communicate the TriggerEnter from the gameobject to my plain c# class?
    I'll read up more on SerializeReference.
     
  4. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Just turn the rigidbody/colliders off. It's literally just a few lines of code.

    Via code? I can't tell you how to do everything here. How you set up a combat system is entirely up to you to engineer it, and it's not that complicated to get a trigger collider to fire off an event to anything that's listening.

    Don't overthink this too much. Spend more time coding than thinking about it. Remember an item/inventory system is going to live more in the pure C# world than it is in the Unity world.
     
  5. Davicro

    Davicro

    Joined:
    Nov 28, 2018
    Posts:
    15
    Yeah, you're right, I should just start doing it and things will fall into place. Thanks for the help
     
  6. Stardog

    Stardog

    Joined:
    Jun 28, 2010
    Posts:
    1,886
    This is the typical issue from not separating data or "the thing" from its visuals.

    The pickup should be a pickup, not the thing itself. It should contain the ID of the thing it's representing (or reference to ScriptableObject). If you want to edit it by hand it should be a prefab. It disappears/destroys when "picked up".
     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    Yes, an unfortunately all-too-common problem given how quick and easy it is to throw piles of wonderful connected things together in Unity until you have a stringy gooey mess of MonoBehaviours everywhere!

    I love it, I do it all the time, but it can be a bit of a siren's call if you don't resist. I have refactored SO much code to make it not be this way after a super-hasty crazy-fast gamejam session. :)

    Meanwhile, for OP, let me give you a framework to think about your problem space more clearly, to hopefully allow you to attack it in small digestible pieces until it is all unkinked and working for you:

    These things (inventory, shop systems, character customization, dialog tree systems, crafting, etc) are fairly tricky hairy beasts, definitely deep in advanced coding territory.

    Inventory code never lives "all by itself." All inventory code is EXTREMELY tightly bound to prefabs and/or assets used to display and present and control the inventory. Problems and solutions must consider both code and assets as well as scene / prefab setup and connectivity.

    Inventories / shop systems / character selectors all contain elements of:

    - a database of items that you may possibly possess / equip
    - a database of the items that you actually possess / equip currently
    - perhaps another database of your "storage" area at home base?
    - persistence of this information to storage between game runs
    - presentation of the inventory to the user (may have to scale and grow, overlay parts, clothing, etc)
    - interaction with items in the inventory or on the character or in the home base storage area
    - interaction with the world to get items in and out
    - dependence on asset definition (images, etc.) for presentation

    Just the design choices of such a system can have a lot of complicating confounding issues, such as:

    - can you have multiple items? Is there a limit?
    - if there is an item limit, what is it? Total count? Weight? Size? Something else?
    - are those items shown individually or do they stack?
    - are coins / gems stacked but other stuff isn't stacked?
    - do items have detailed data shown (durability, rarity, damage, etc.)?
    - can users combine items to make new items? How? Limits? Results? Messages of success/failure?
    - can users substantially modify items with other things like spells, gems, sockets, etc.?
    - does a worn-out item (shovel) become something else (like a stick) when the item wears out fully?
    - etc.

    Your best bet is probably to write down exactly what you want feature-wise. It may be useful to get very familiar with an existing game so you have an actual example of each feature in action.

    Once you have decided a baseline design, fully work through two or three different inventory tutorials on Youtube, perhaps even for the game example you have chosen above.

    Breaking down a large problem such as inventory:

    https://forum.unity.com/threads/weapon-inventory-and-how-to-script-weapons.1046236/#post-6769558

    If you want to see most of the steps involved, make a "micro inventory" in your game, something whereby the player can have (or not have) a single item, and display that item in the UI, and let the user select that item and do things with it (take, drop, use, wear, eat, sell, buy, etc.).

    Everything you learn doing that "micro inventory" of one item will apply when you have any larger more complex inventory, and it will give you a feel for what you are dealing with.

    Breaking down large problems in general:

    https://forum.unity.com/threads/opt...n-an-asteroid-belt-game.1395319/#post-8781697
     
    CodeRonnie likes this.
  8. Davicro

    Davicro

    Joined:
    Nov 28, 2018
    Posts:
    15
    Hey kurt-dekker! Thanks so much for responding to this thread, i've seen you in many other threads dealing with inventory problems and you've been very helpful in figuring out what I need.
    The inventory part is the part I sort of already have figured out, in fact I have a pretty good inventory/slot system with UI, and since most of it is abstracted I can just reuse it. The thing I need to figure out now is how to communicate my plain c# item class with things such as the item's model and scripts that it may contain, since for example, a weapon will have a "Weapon" script that will handle TriggerEnter and such. But that's for me to figure out. I'll start small like you said and scale from there.
     
  9. Davicro

    Davicro

    Joined:
    Nov 28, 2018
    Posts:
    15
    Oh and since I'll be using inheritance for different item behaviours I guess I need to link the ItemData with some type. I think I'll use generics for that:
    ItemData<T> : ScriptableObject where T : Item

    and same for the item
    Item<T> where T : ItemData<Item>

    Now that i'm writing it out it seems kinda messy, but I can't think of any other way to get the correct Item class from the ItemData
     
  10. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    No don't use generics here. You code will look like hell. This also means you can't have a collection of different types of Items or ItemData, so you can throw the advantages of polymorphism out the door.

    The relationship should be one way. The item scriptable objects just hold data. That's all they do. They don't 'do' anything, they don't talk to anything, just hold data. Other things can reference this data to know of the immutable information about an item, but the item itself has zero care about anything outside.

    The plain C# Item wrapper stores a reference to the item asset and represents a 'live' item in the game world. All information about the item is routed through the wrapper; and it probably shouldn't even expose the actual item directly in any way. The wrapper contains all mutable information about an item, such as durability, ammo, etc.

    This is what I was getting at with SerializeReference. No need for making tons of item classes with inheritance. You just need the one class, and you can serialise in different plain C# classes into a collection with polymorphism to mix and match behaviour. Kinda looks like this in a shelved project of mine:
    upload_2023-6-27_18-36-48.png

    (Odin Inspector is doing the magic here).
     
  11. Davicro

    Davicro

    Joined:
    Nov 28, 2018
    Posts:
    15
    While this looks very cool, i'd like to go for a (free) and more traditional approach
     
  12. Davicro

    Davicro

    Joined:
    Nov 28, 2018
    Posts:
    15
    Although, I do think it'd be interesting to use a component based system for items instead of inheritance
     
  13. OutDoorsScene

    OutDoorsScene

    Joined:
    Sep 9, 2022
    Posts:
    140
    Personally, I wouldn't know how to get much control of an inventory system without heavy use of game objects.
    ezgif.com-optimize.gif
     
  14. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Game Objects are just part what's going on the 'physical' world, which is should be backed by a large degree of data not tied to any Unity objects.

    An inventory UI, whether its uGUI or UI Toolkit, should just be displaying and shuffling around data. If your inventory is actually represented by game objects and their hierarchy... that's just making life very, very hard on yourself.
     
    Kurt-Dekker likes this.
  15. OutDoorsScene

    OutDoorsScene

    Joined:
    Sep 9, 2022
    Posts:
    140
    Originally, the inventory was going to be pure c#. But I couldn’t figure out how to display swapping items in UI.

    Now it’s a forloop that checks 5 squares, that checks if a item is inside the square.
    These items are instantiated and destroyed when picking up/dropping items.

    While it does feel fragile, all interactions (changing/reading) with the inventory are done with methods and as Todd Howard used to say “It just works”
     
  16. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    These are two completely separate layers of design here. The user interface handles the input and tells the data layer what to do. It's not particularly hard to move things around in a regular
    List<T>
    .

    Inventories are like ogres, they have layers.
     
    CodeRonnie and Kurt-Dekker like this.
  17. OutDoorsScene

    OutDoorsScene

    Joined:
    Sep 9, 2022
    Posts:
    140
    It’s the moving, displaying or not displaying UI that I’m currently struggling with.

    An inventory separate from UI would probably be something I could achieve 5 years later.

    With my current inventory accessing or changing something requires a simple method. So the bulky code is out of sight.
     
  18. Davicro

    Davicro

    Joined:
    Nov 28, 2018
    Posts:
    15
    The way I went about the Inventory's UI is that each slot has an event
    OnSlotUpdate
    , and each UI slot just subscribes to that and updates its text and icon whenever the event is fired. If you want to interact with your inventory, all you have to do is access the inventory (for example when a UI slot is clicked), and do whatever you want with it, since it will update automatically through the subscribed events. That way you have an inventory system that works by itself, and a UI that simply accesses the inventory
     
    CodeRonnie likes this.
  19. Davicro

    Davicro

    Joined:
    Nov 28, 2018
    Posts:
    15
    So I'm a bit embarrased to say this, but I'm stuck (again). I finally implemented the item system after much overthinking, and it works! This is a quick rundown of how I implemented it:

    I separated items into data and instance:
    - ItemData (the item's definition, scriptable object): It contains data such as name, description, stats and stat modifiers, and components. It holds reference to the item's pickup and model prefabs. It defines the item.
    - Item (the item's instance, c# class): It wraps the ItemData and instantiates its values, making a copy of the components and initializing them. This is the actual item in the game.

    So, as you can see I took inspiration from spiney and used components to define item's behaviour. The thing is, since I'm going completely C# here I can't take advantage of unity's built-in Monobehaviours, so to solve this I created my own GameObject Component system, I called it "ModuleObject" and "Modules". Each ModuleObject has a list of Modules, and the ModuleObject calls upon the Modules' methods. So: ModuleObject.Update() -> List<Modules>.Update(). And, I mean it does work, but I feel like I wayyyy over-engineered this. I mean sure, this is how I'd do it without working with unity, I'd probably create my own ECS and go from there, but this is Unity we're talking about, it already has a GameObject Component system that I should be taking advantage of. It just feels a bit redundant to re-implement something that's already implemented. The reason why I made this whole system in the first place is because while in an inventory, the item is really nothing but data. It doesn't have a representation in the world, but it still has to have all its instance data and all its components.

    Am I overcomplicating things? I'd really just like to know what different approaches people have used for their items, since tutorials on youtube are always very half-baked and never really implement item behaviour.
     
    Last edited: Jul 28, 2023
  20. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    So you didn't want to use the MonoBehaviour / Component system, in use by millions of people and tried and tested and understood and documented in literally millions of documents all over the web, example code, etc.

    And you wrote your own?

    Yes.

    You must be confused about the purpose of tutorials. They're not to give you a final product. That's not a thing. The point of a good tutorial is to teach one thing (or a small subset of related things) in the clearest simplest un-cluttered straightforward way possible. The point is for YOU to learn everything necessary to take the principles in the tutorial and use them to engineer your own solution.

    This is software engineering after all.

    Did you even consider any of the many points, which WILL apply whether you consider them or not, in my original reply about inventories??

    https://forum.unity.com/threads/inventory-item-system.1453261/#post-9107089

    The engineering problems of making an inventory are complex and interconnected. My post is simply to help you reason about them in a systematic approach so that you can engineer a solution suitable to your needs. Remember, the minecraft inventory wouldn't work in skyrim, and vice-versa.
     
  21. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    I mean when you've outlined the data/behaviour of all your items in a generally pure C# fashion, or with scriptable objects, what's the best way to turn all this pure C# data into monobehaviour components? May as well just keep it all in the C# world rather than have to bridge that gap.

    It's not that crazy honestly.
     
  22. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    I wouldn't modify the pure C# data. That part is great.

    I would make a MonoBehaviour-based solution to present that data, perhaps allowing it to be modified.

    I would have a UI manager that could decide which MonoBehaviour-based presentation prefabs might be stood up at runtime in order to present and manage the data.

    And to back up the pure data side of things, I would make a robust repository that keeps this data with a clearly-defined API surface to create, get, set, list, filter, search, delete, merge, etc. the underlying data.

    This lets you build value at each step of the API, and helps hide underlying data details. Definitely do not sprinkle data accesses throughout the code, such as adding / removing from collections, which is a common thing to splatter all over the codebase.

    And I would not name things with generic words like "Module" and "ModuleObject." Names like that almost always make it hard to reason about your original intent when you revisit the code a month or two from now.
     
  23. Davicro

    Davicro

    Joined:
    Nov 28, 2018
    Posts:
    15
    I know... I'm sorry. Since I didn't want an actual object in the scene when the item is in the inventory I thought it would be a good idea to have it be pure data, but I see how that conflicts when having to implement behaviour.

    Of course! I have inventories to store items, UI to display them and move them between slots, stacking, etc... The inventory part of it is not what I'm struggling with, it's what structure items should have.

    I don't understand this.

    The way I see it, there's three ways to make items: The item is a GameObject, the Item is a scriptable object or the item is a pure c# class. For my game, the scriptable object approach doesn't work since I need instances of these items, and scriptable objects hold static data. Okay, so use scriptable objects for the item data, and then something else for the item instance. If I use c# classes for instances I'm back where I started, having to re-invent the wheel. So the other option is having a gameobject be the item. You'd be making prefabs of the items, which would have a reference to the ItemData, and the ItemData would have a reference to the prefab, which at that point why not store all the data in the prefab directly? And why separate the pickup from the item itself, just have the prefab be both, which places me back to my old implementation.

    I apologize for my stupid implementations, I'm still trying to learn unity.
     
  24. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    That's not the correct way to think about it.

    The user owning or not owning an item is in a pure C# class, probably a collection of "things I own now," and this data is likely stored in the overall "user saved state."

    The qualities of this item might be a ScriptableObject that defines its game effects (power, weight, cost, etc.)

    The presentation of this item lying around in the world might be a GameObject with a Mesh or Sprite.

    The presentation of this item in a UI might be a GameObject with UI-like components.

    These things are complex, I'm not kidding.

    Could you just stick it all in fields in a single master MonoBehaviour and hang it on a GameObject that has a presentation UI portion and an in-scene lying around mesh part? Yeah... yeah, I guess technically you "COULD" .. but wow, that would make things EXTREMELY difficult to work with!
     
    Stardog and mopthrow like this.
  25. OutDoorsScene

    OutDoorsScene

    Joined:
    Sep 9, 2022
    Posts:
    140
    I decided to get my inventory inside of a single array instead of multiple gameobjects.

    Here's a small portion of my code,
    Code (CSharp):
    1. [System.Serializable]
    2. public class Inventory
    3. {
    4.     public ITEMS Item;
    5.     public bool IsSelected;
    6.     public SingleItem ItemUI;
    7.  
    8.     /// <summary>
    9.     /// creates the inventory
    10.     /// </summary>
    11.     public Inventory()
    12.     {
    13.         Item = new ITEMS();
    14.         IsSelected = false;
    15.         ItemUI = null;
    16.     }
    17. }
    Code (CSharp):
    1.  private void CreateInventory()
    2.     {
    3.         inventory = new Inventory[5];
    4.         for (int i = 0; i < inventory.Length; i++)
    5.         {
    6.             inventory[i] = new Inventory();
    7.         }
    8.     }
    Code (CSharp):
    1.  public static void SwapItem(int SlotA, int SlotB)
    2.     {
    3.         Inventory inventoryA = inventory[SlotA - 1];
    4.         Inventory inventoryB = inventory[SlotB - 1];
    5.  
    6.         //if item is empty.
    7.         if (inventoryB.ItemUI == null)
    8.         {
    9.          
    10.             inventoryB.Item = inventoryA.Item;
    11.             inventoryB.ItemUI = inventoryA.ItemUI;
    12.             MoveItemUI(inventoryB.ItemUI, SlotB);
    13.  
    14.             inventoryA.Item = new ITEMS();
    15.             inventoryA.ItemUI = null;
    16.  
    17.        
    18.  
    19.         }
    20.         else
    21.         {
    22.             ITEMS A_SwapItem = inventoryA.Item;
    23.             SingleItem A_SwapUI = inventoryA.ItemUI;
    24.  
    25.             ITEMS B_SwapItem = inventoryB.Item;
    26.             SingleItem B_SwapUI = inventoryB.ItemUI;
    27.  
    28.             inventoryA.Item = B_SwapItem;
    29.             inventoryA.ItemUI = B_SwapUI;
    30.             MoveItemUI(inventoryA.ItemUI, SlotA);
    31.  
    32.             inventoryB.Item = A_SwapItem;
    33.             inventoryB.ItemUI = A_SwapUI;
    34.             MoveItemUI(inventoryB.ItemUI, SlotB);
    35.         }
    36.     }
    The UI behaves the same as my previous post. But now, accessing the inventory requires 1 dot instead of 3.
     
  26. Davicro

    Davicro

    Joined:
    Nov 28, 2018
    Posts:
    15
    And I thought it'd be trivial :(

    I don't know if it's a lot to ask, but I'm really interested in how your item system works, if you wouldn't mind sharing. Here are a few questions: Are you using your own component system? Do you use scriptable objects for data and then wrap that in a plain class for instances? Are your items pure c#? Thanks :D
     
  27. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    I mean I posted about it earlier in this thread. They're scriptable objects with a polymorphic list of classes that represent 'item components'. A wrapper class is used to represent an actual instance of an item, which itself has two collections that represent mutable 'modifications' (such as ammo, durability, etc), and a list of immutable modifications (buffs, upgrades, etc).

    Really the crux of it is just taking advantage of OOP's biggest strength: polymorphism. The rest was just general solid programming.

    Though this was an isometric survival-craft game with no combat. It was heavily focused on crafting, so, there was need for such systems while there wasn't a lot going on similar to your project. Items dropped on the ground had no physics, for example.

    I haven't worked on the project in a good while, admittedly. Since then I've toyed around with different item architectures, such as a prototype pattern system (which could also be regarded as 'multiple inheritance'):
    upload_2023-7-30_1-7-20.png
     
    Davicro and Kurt-Dekker like this.