Announcement

Collapse
No announcement yet.

Help modeling more elaborate Item/Inventory Systems

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Help modeling more elaborate Item/Inventory Systems

    Hello,

    I have been scrambling about this issue for a while now. I am attempting to build a more elaborate item system -- one that includes basic inventory items, items with stats modification, equippables, weapons, and armor. However, I am unable to comprehensively model such system. Most tutorials online only teach basics such as picking up an actor and adding a representation to an inventory list without taking a look at a bigger picture, so I am lost here.

    Requirements
    I am using a data driven model as I am attempting to model and RPG game. Some world items can be picked up or "acquired" by other means and are then stored in a player inventory. However, I require the following types of items:
    • Basic Inventory Items - Those are the ones that are just sitting in the player's backpack. Ideally, they can be dropped or used to trigger key events.
    • Active Items - Those are inventory items with modifiers -- if the player has them on their list of active item, they apply their effect. Some effects are passively applied, while others need to be activated. (Think of item slots found in MOBAs)
    • Equippable items - Unlike the two above, these items CAN have a world representation. When the player index an equippable item, I expect it to display a mesh, or spawn an actor at a pre-determined socket location.
    • Armor - They sound equippable...but they are simply an additional mesh component on the player class with stats modification.
    • Weapons - Equippables with elaborate firing abilities.

    What I Have Setup
    • I have a Buff class that derives from UObject. This class has derived classes that can modify a player's attribute. Each player class has a BuffManager component that handles the addition and removal of buffs based on expiry events from the UBuff object. My intentions was to give Active Items a list of UBuff objects that can be applied to the player.
    • An ItemFactory exists to facilitate the creation of InventoryItems and WorldItems.
    • My base InventoryItem class is no more than an abstract UObject that contains a struct called FBaseItemInfo -- this struct has information such as the item name, description, icon, and most importantly, its ID in my item datatable.
    • I also have an Inventory component class that handles the addition of InventorySlots -- which is a wrapper that holds a FBaseItemInfo struct, and other associated slot information (stack size, curr amount, etc).

    Problems
    • I am unsure if my base InventoryItem class must be a UObject, Actor, or ActorComponent. These items are no more than numbers and text on a UI. However, if Equippables are a subclass of InventoryItems, then they NEED some form of Actor representation. Forcing all InventoryItems to be Actors just for that cause makes it seem like a very impractical and unnecessary overhead though.
    • Are Armors considered as subclasses of Equippables? From a player perspective, they seem equippable, but they are just there to modify or add a mesh component to the player while apply a UBuff. Whereas my perception of equippables is of independent items/actors that attach in the player's hand.
    • How do you keep such system as procedural and maintainable while abiding by the data driven model?
    • I am avoiding adding an Actor reference or UClass variable for Equippable items as a cheat for spawning a visual representation of the item - Is it wrong to think that way?
    • If I where to categorize the inventory by type, would it be ideal to add an enum category property for the base InventoryItem class, or perhaps use the datatable to include information such as the TSubClass of that item ID and use conditional handling in the UI accordingly?


    I think it's becoming evident that the thing giving me trouble here is the handling of equippable items while keeping the system as streamlined and logical as possible. Handling all these items independently is not the issue -- I can always create a weapon class and give it the appropriate behavior, which is the kind of thing found in most tutorials. But creating this system of things with flexibility and maintainability in mind is where it gets messy.

    Thanks for your patience!

    #2
    I was struggling with this for a while, feels a bit like trying to be all things. The end result I went for is having inventory items be a struct. It has an FName ID, int32 Quantity, TArray<mods> and then a category. I started with using an enum (item, weapon, head, etc...) then I swapped to gameplay tags.

    The actual inventory data is just a TArray of these structs. Then when I create the inventory widget it calls to an item info data table with the ID for any actual info and on the datatable is the type of widget to create so it might create a weapon widget or a usable widget. The class handling the quipped items then can also take in a FName ID (either from a struct or a widget) and do two things. It first checks the gameplay tag to see if it is valid to be equipped in which slot and also creates the actual actor at that point from a datatable. I've done this both with a separate datatable and holding the class reference on the item info datatable in different projects

    Hope that makes sense? Its allowed me to quickly drop in a new items by just editing the datatables no problem.

    TL; DR separation of concerns: Three classes - one handling the inventory as data, one handling the inventory as UI, and finally one handling the items as Actors/Components only when needed in game space.
    Last edited by adgoodwin; 12-26-2017, 06:35 AM.

    Comment


      #3
      Originally posted by adgoodwin View Post
      I was struggling with this for a while, feels a bit like trying to be all things. The end result I went for is having inventory items be a struct. It has an FName ID, int32 Quantity, TArray<mods> and then a category. I started with using an enum (item, weapon, head, etc...) then I swapped to gameplay tags.

      The actual inventory data is just a TArray of these structs. Then when I create the inventory widget it calls to an item info data table with the ID for any actual info and on the datatable is the type of widget to create so it might create a weapon widget or a usable widget. The class handling the quipped items then can also take in a FName ID (either from a struct or a widget) and do two things. It first checks the gameplay tag to see if it is valid to be equipped in which slot and also creates the actual actor at that point from a datatable. I've done this both with a separate datatable and holding the class reference on the item info datatable in different projects

      Hope that makes sense? Its allowed me to quickly drop in a new items by just editing the datatables no problem.

      TL; DR separation of concerns: Three classes - one handling the inventory as data, one handling the inventory as UI, and finally one handling the items as Actors/Components only when needed in game space.
      Hi! Thanks for the response,

      Luckily, that is almost identical to what I currently have. A couple of concerns though:

      First, my actual inventory container is a TMap <FName ID, FInventorySlot slots>. I thought it will make searching items and doing manipulation and updates to the inventory very easy with the almost direct access from the map. However I feel like it's biting me back when I start thinking about the UI. If I were to drag items around and swap them in different places, it would make sense to use array indices to manipulate the slot information no? Is interfacing the inventory using a Map as difficult as I think it is?

      Second, my main concern was knowing whether an item is equippable, and how do you actually "equip" the thing and its effect on the player. Does the equip item class dynamically spawn mesh components on the instigating owner. And even then, an equippable has functionalities, especially weapons -- how would you model that effect on the player dynamically.

      Since yesterday, I changed my approach to do the following:
      • Declare 2 Static Mesh components as part of my BaseCharacter class. The 2 components are attached to predefined sockets in the skeletal mesh. In doing so, I define the existence of equippable slots, located at either hands of a character.
      • The EqupItem class has a flag indicating which socket it resides in. Upon calling the equip function, it swaps the associated Static Mesh component on the instigator to the model it contains.
      • For that to work, I had to define a variable of type StaticMesh* for all instances of an EquipItem -- this is where I become unsure. Should I relegate this task to the DataTable instead so that I do not waste on memory? Is it negligible ? How much slower would access to the DataTable be anyways.
      • Finally, the Equip function can contain the logic to modify aspect of the player (if it's a weapon, maybe set the player's state enum to go to combat mode). To be honest, I have no clue if the player's combat should be handled via a Combat component or as part of the BaseCharacter, but I will worry about that later.

      Does this sound like it's heading in the right direction?

      Comment


        #4
        With the container I have TArray<ItemStruct>, the Inventory class with that in has an event/delegate that when the array is updated it sends out a InventoryUpdated(int Index) message. The UI Inventory is bound to this on creation so it gets any changes. Likewise the UI already has an associated TArray<SlotWidget> and obviously has an inventory reference. When things happen in the UI it calls the reference of inventory and changes it - Any changes to any slots is transmitted as an event. I went with this so I could have multiple Inventory UI's picking up the change. Say the inventory is "open" and you ahve a sho widget that has a copy of the inventory. All things update off the one change.

        The references for the class and the static mesh and any other in-game reference all live on the datatable. The lookup on a datatable is fast and memory efficent using TAssetPtr's for lazy loading and only gets checked the once on Actor creation. It also saves having to assign anything until creation of the actor so I don't need a long sword, broad sword etc class. The way to look at datatables is they contain anything that is shared and immutable between objects of the same type. Mesh, weight, texture, cost.... but durability is a per item stat (though max durability is a datatable is a set standard).

        With tracking if something is say equipable say or usable I recommend investigating gameplay tags. Easy to edit in datatables too and can be stacked so an item can have multiple "flags".

        As for the last point, to be honest I don't know if its best but i too am handling things with components. So if it can attack or take damage I drop it on an item. Monster, combat component. destructable door or crate, combat component.Just a quick way to mark something up with functionality.

        Comment

        Working...
        X