Extending the Inventory and ItemType/Category system.

#1
Hi Justin,

Since I am making an RPG I would like to have a robust inventory system. I was planning to use Inventory Pro, but since there isn't an integration package for UCC and IV Pro, I am taking into account maybe extending the UCC Inventory instead.

Your current implementation has only equipable items, mostly weapons. The itemCategory could be used to create categories such as Weapons, Materials, Consumables, Tools, Armor, etc.. I could then make, for example, a health potion itemType under consumable.

Let's say that I have an inventory filled with many many weapons and consumables and that the player should choose 4 consumables and 3 weapons which he/she can easily use/swap during gameplay (no need to go to the inventory window). My character can have 4 (D-pad directions) consumable items in his pouch (equipped), and 3 "ready to be equipped" weapons that you can switch through with a button. To do such a thing I would need a new inventory class right?

There is also the need to have item subcategories. For example, a small or a big health potion would be a Consumable > potion > health, while a fish stew would be a Consumable > Food. To give another example you could have a bronze pickaxe be a Tool > Pickaxe and a silver axe be a Tool > axe. You would then be able to "soft equip" (actually equip when using the relevant ability: mine/chop) one tool of each tool category, in your inventory "Tool" tab.

For itemTypes such as health potions, I would need to specify a healing amount. since the health potion is not an equipable item I wouldn't create health potions using the Item Manager. So I would probably want to extend ItemType too.

Having subCategories would require to extend ItemTypes and ItemCategories. How would that work with the ItemTypes Manager window?

Is it a good idea to extend the UCC inventory and item/Type/Category classes or should I consider making my own inventory system and use the UCC inventory only for equipable items. Should I reconsider the entire thing and stick to getting Inventory Pro to integrate correctly with UCC?


At this point I wouldn't know if I would want to use the itemAbility or the Ability to use Consumable items considering that in my case they'll probably only play a simple animation and give the character health/man and stat boost, although having consumable scrolls that shoot magic abilities could be cool.

I would guess that making items that are only used for quests or crafting should not be too hard since they are just numbers, it could be set up similarly to the bullets in your examples.


On another note, there is something I am slightly confused about when it comes to items. The inventory only stores itemTypes right? so if you pick up an item at run time, the item will not only be added to the character but its itemType will be added to the inventory. So now if I change scenes, I save my inventory, and on the other side I have the itemType in the inventory but I don't have the weapon on my character (can't equip something that does not exist). This inconsistency feels like a flaw to me. Would I need a script that spawns the equipable weapons in my inventory every time on start? How would I know which weapon to spawn if I have two weapon prefabs using the same itemType (normal, upgraded)? Maybe I should only have one item prefab per itemType.

Please let me know if I misunderstood anything about the item solution.


What are your thoughts on the subject?

Thank you for your time
 

Justin

Administrator
Staff member
#2
Inventories are basically another asset on their own so the inventory in the controller is basic by design. I recommend that you implement your own inventory by inheriting the InventoryBase class and adding any new features to that. This will involve creating all new editors and you can also subclass the ItemType ScriptableObject to add more features to that.

Would I need a script that spawns the equipable weapons in my inventory every time on start? How would I know which weapon to spawn if I have two weapon prefabs using the same itemType (normal, upgraded)?
Yes, you'd need a save/load system for that. In the case of an upgraded item you'd have two different ItemTypes, or have a property within your inventory that specifies its upgrade level and you can then load that in.
 
#4
Hi Justin,

I extended the inventory to add a small potion item, which is consumable and can be used from the inventory.

I was wondering if you could have a quick look at how I implemented it and tell me if there are things I should change before I start adding too much content.
The part I struggle the most with is the editor scripting, you might be able to help me there.

First thing I did was adding my own Inventory class. I added a reference to the attribute manager so that I can have the consumable items interact with it (e.g heal). I also added a reference to the item collection (the one used by the Item Set Manager) so that I can get the item categories name/ID to populate the m_Category Dictionary. I prefer using Enums instead of strings because I make a lot of spelling mistakes.
C#:
public class RPGInventory : InventoryBase
    {
        private Dictionary<ItemType, Item>[] m_ItemTypeItemMap;
        private Dictionary<ItemType, float> m_ItemTypeCount = new Dictionary<ItemType, float>();
        private Item[] m_ActiveItem;
        private AttributeManager attributeManager;
        private ItemCollection itemCollection;

        public Dictionary<ItemTypeCategory,ItemCollection.Category> m_Categories;
1) Does the item collection have all the items for my character? or should I use multiple item collections per character (example equippable, consumable)? The main reason I get the itemCollection is to get the category IDs for my items. I need the IDs to organize my inventory in the UI. Is there a better way to do this?

The other thing I did for my inventory was adding a call for the consumable item function (example heal)
C#:
protected override void UseItemInternal(ItemType itemType, float count)
        {
            var existingAmount = 0f;
            if (!m_ItemTypeCount.TryGetValue(itemType, out existingAmount))
            {
                Debug.LogError("Error: Trying to use item " + itemType.name + " when the ItemType doesn't exist.");
                return;
            }

            //I added this
            if(existingAmount > 0)
            {
                ConsumableItemType consumableItemType = itemType as ConsumableItemType;
                if (consumableItemType)
                {
                    consumableItemType.UseConsumableItem(attributeManager);
                }
            }
            //

            m_ItemTypeCount[itemType] = Mathf.Clamp(existingAmount - count, 0, itemType.Capacity);
        }

I also extended the ItemType with a ItemTypeBase, which I will use for all my items.
C#:
 public enum ItemTypeCategory
    {
        Weapon,
        Consumable
    }
    /// <summary>
    /// An ItemType is a static representation of an item. Each item that interacts with the inventory must have an ItemType.
    /// This Item type base is the base for any item type extension
    /// </summary>
    public class ItemTypeBase : ItemType
    {
        //name? already public?

        [Tooltip("The Sprite that will be used to display the item Icon")]
        [SerializeField] protected Sprite m_Icon;
        [Tooltip("The rarity of the item 0 to 5")]
        [Range(0,5)]
        [SerializeField] protected int m_Rarity;

        [Header("Restrictions")]
        [SerializeField] protected bool m_IsSellable;




        public Sprite Icon { get { return m_Icon; } set { m_Icon = value; } }
        public int Rarity { get { return m_Rarity; } set { m_Rarity = value; } }
        public bool IsSellable { get { return m_IsSellable; } set { m_IsSellable = value; } }
    }
I inherit the itemTypebase to make my ConsumableItemType. Note how I need the attribute manager to get a reference to the attribute
C#:
public enum ConsumableEffect
    {
        Add,
        Restore,
        Decrease
    }

    [System.Serializable]
    public class AttributeEffect
    {
        public string m_AttributeName;
        public float value;
        public bool multiplier;
        public ConsumableEffect consumableEffect;

        private Attribute m_Attribute;

        public void UseEffect(AttributeManager attributeManager)
        {
            if (string.IsNullOrEmpty(m_AttributeName))
            {
                Debug.LogError("Attribute name was not set in the attribute effect");
                return;
            }

            if (attributeManager == null)
            {
                Debug.LogError("Attribute manager is null for attribute effect");
                return;
            }

            m_Attribute = attributeManager.GetAttribute(m_AttributeName);

            switch (consumableEffect)
            {
                case ConsumableEffect.Add:
                    Add();
                    break;
                case ConsumableEffect.Restore:
                    Restore();
                    break;
                case ConsumableEffect.Decrease:
                    Decrease();
                    break;
            }
                
        }

        public void Add()
        {
            m_Attribute.MaxValue += value;
            m_Attribute.Value += value;
        }

        public void Restore()
        {
            m_Attribute.Value += value;
        }

        public void Decrease()
        {
            m_Attribute.MaxValue -= value;
        }
    }


    /// <summary>
    /// The consumable Item type is an itemType that can be equipped to the pouch and that can also be used in the inventory.
    /// </summary>
    public class ConsumableItemType : ItemTypeBase
    {
        [Tooltip("Audio Clip when the item is consumed")]
        [SerializeField] protected AudioClip m_AudioClip_Use;//TODO create class with pitch volume loop options
        [SerializeField] protected AttributeEffect[] m_AttributeEffect;

        //TODO look into attribute modifiers

        public void UseConsumableItem(AttributeManager attributeManager)
        {
            foreach (AttributeEffect effect in m_AttributeEffect)
            {
                effect.UseEffect(attributeManager);
            }
        }
    }
I had to extend the editor manager to create the new ConsumableItemType. I basicly copy pasted your code and added some stuff
C#:
public enum ItemTypeTypes
    {
        ItemType,
        ConsumableItemType
    }
    /// <summary>
    /// The ItemTypeManager will draw any ItemType properties
    /// </summary>
    [Serializable]
    [ManagerMenuItem("Item Types 2", 3)]
    public class ItemTypeManager : Manager
    {
I had to add a pop for the type of ItemType in the draw function
C#:
 private void DrawItemTypes()
        {
            var itemTypes = m_ItemCollection.ItemTypes;

            EditorGUILayout.BeginHorizontal();
            GUI.SetNextControlName("ItemTypeType");
            m_ItemTypeType = (ItemTypeTypes)EditorGUILayout.EnumPopup("ItemType",m_ItemTypeType);
            GUI.SetNextControlName("ItemTypeName");
            m_ItemTypeName = EditorGUILayout.TextField("Name", m_ItemTypeName);
            GUI.enabled = !string.IsNullOrEmpty(m_ItemTypeName) && (m_ItemTypeTreeView.TreeModal as ItemTypeCollectionModal).IsUniqueName(m_ItemTypeName);
            if (GUILayout.Button("Add", GUILayout.Width(100)) || (Event.current.keyCode == KeyCode.Return && GUI.GetNameOfFocusedControl() == "ItemTypeName"))
            {
                // Create the new ItemType.
                var itemType = ScriptableObject.CreateInstance(m_ItemTypeType.ToString()) as ItemTypeBase;
                itemType.name = m_ItemTypeName;
2) Now I got a problem with the DrawDetails function. How can I add all the serializable fields without having to do it manually for every new Item Type extension I add? It seems very inefficient to do manually, if possible I would prefer it to just draw the fields as it does in the inspector for normal scripts. In the meantime, I use the inspector in debug mode to change the values of my scriptable object instances.
1549049304859.png
 

Attachments

#5
The post was too long here is the rest:

With these extensions I got myself a small potion item. Now to display and use the items in the UI I made two scripts. The menu and the item. The game menu basically instantiates the consumable items in a list:
C#:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Opsive.UltimateCharacterController.Inventory;
using SleepingPenguinz.PhaeProject.OpsiveUCCSupport;
using UnityEngine.UI;

namespace SleepingPenguinz.PhaeProject.UI
{
    public class GameMenuUI : MonoBehaviour
    {
        public enum Window
        {
            MainMenu,
            ItemMenu,
            EquipmentMenu
        }

        private RPGInventory m_inventory;
        private List<Window> m_ActiveWindow;
        public GameObject ItemContents;
        public GameObject ItemUIPrefab;

        private void Awake()
        {
            m_inventory = FindObjectOfType<RPGInventory>();

            if (!m_inventory)
            {
                Debug.LogError("Inventory not found by Game Menu UI");
            }
        }

        private void OnEnable()
        {
            m_ActiveWindow = new List<Window>() { Window.MainMenu };
        }

        public void OpenWindow(/*Window window*/)
        {
            /*m_ActiveWindow.Add(window);*/
            ItemCollection.Category consumableCategory;
            if(! m_inventory.m_Categories.TryGetValue(ItemTypeCategory.Consumable, out consumableCategory))
            {
                Debug.LogError("Consumable item category not found in item collection");
            }

            //Remove previous items
            int ItemContentsChilds = ItemContents.transform.childCount;
            for (int i = ItemContentsChilds - 1; i >= 0; i--)
            {
                GameObject.Destroy(ItemContents.transform.GetChild(i).gameObject);
            }

            foreach (ItemType itemType in m_inventory.GetAllItemTypes())
            {
                if (itemType.CategoryIDMatch(consumableCategory.ID))
                {
                    ItemUI itemUI = Instantiate(ItemUIPrefab, ItemContents.transform).GetComponent<ItemUI>();
                    itemUI.Init(m_inventory, itemType as ItemTypeBase, (int)m_inventory.GetItemTypeCount(itemType));
                }
            }
        }

        public void OpenGameMenu()
        {
            gameObject.SetActive(true);
        }

        public void CloseGameMenu()
        {
            gameObject.SetActive(false);
        }
    }
}
The itemUI is the script atteached to the spawned UI objects and draw the item and lets you use the item (called when the UI button is pressed). Note that I have to pass the inventory, itemtype and amount just so that I can display and call the use Item function.
C#:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using SleepingPenguinz.PhaeProject.OpsiveUCCSupport;

namespace SleepingPenguinz.PhaeProject.UI
{
    public class ItemUI : MonoBehaviour
    {
        public Image icon;
        public TextMeshProUGUI nameTMP;
        public TextMeshProUGUI amountTMP;
        

        private ItemTypeBase itemType;
        private int amount;
        private RPGInventory inventory;

        public void Init(RPGInventory _inventory, ItemTypeBase _itemType, int _amount)
        {
            inventory = _inventory;
            itemType = _itemType;
            amount = _amount;
            Draw();
        }

        public void UseItem()
        {
            amount--;
            inventory.UseItem(itemType, 1);

            Draw();
        }

        private void Draw()
        {
            if (amount <= 0)
            {
                Destroy(gameObject);
                return;
            }
            icon.sprite = itemType.Icon;
            nameTMP.text = itemType.name;
            amountTMP.text = amount.ToString();
        }
    }

}
The result: View attachment 791
When I press the item my character heals!

Once I get things further I would like to have tabs for each item category (consumable, armor, weapon, material, etc...)
I am pretty happy it works, but it is my first project where I use TPC or make an inventory, so I am absolutely sure that my solution can be improved dramatically.

One more thing I wanted to ask you is about attribute modifiers. I would like to have consumable items that give the character a "strength" boost for 2 minutes for example. If I use attributes to make my character stats, could I use it to change the damage done/received by attacks? And can I add attribute modifiers dynamically to my character, for a limited amount? Can they be stacked?

I haven't found much about attribute modifiers in the documentation, maybe you could explain how I could use them for consumable items.

Thank for taking the time to help me out. I really appreciate it.
 

Justin

Administrator
Staff member
#6
I'm not able to really comment on the actual code without trying it out and really digging into it but I can answer your questions:

Does the item collection have all the items for my character?
Yes, one ItemCollection for the character. Really it should be one ItemCollection throughout your entire project so any character can pick up any weapon.

How can I add all the serializable fields without having to do it manually for every new Item Type extension I add?
You can draw all fields with ObjectInspector.DrawFields.

If I use attributes to make my character stats, could I use it to change the damage done/received by attacks?
Attribute modifiers only work on elements defined within the attribute manager. For any fields you'll need to have some other way of changing the value (such as by using the state system).
 
#7
Hi Justin,

I ran into a problem with the inventory. All the inventoryBase function find items using the correspondent itemType. I would like to change it so that each item correspond to an "InventoryItem". An "InventoryItem" is a class that has an itemType but also has additional information such as a state (upgrade level, durability left, etc..). This means that I would like to be able to add two weapons on my character with the same ItemType but different states. I would use an InventoryItem as the key of a dictionary to find the corresponding item. Currently, my InventoryItem class is like this but I will most likely expand on it:
C#:
public class InventoryItem : IEquatable<InventoryItem>
    {
        protected ItemTypeBase m_ItemType;
        protected Upgrade m_Upgrade;

        public ItemTypeBase ItemType { get { return m_ItemType; } }
        public Upgrade upgrade { get { return m_Upgrade; } }

        public InventoryItem(ItemTypeBase itemType, Upgrade upgrade)
        {
            m_ItemType = itemType;
            m_Upgrade = upgrade;
        }

        public int ID
        {
            get
            {
                return Enum.GetValues(typeof(Upgrade)).Length * m_ItemType.ID + (int)m_Upgrade;
            }
        }

        public override int GetHashCode()
        {
            return ID;
        }
        public override bool Equals(object obj)
        {
            return Equals(obj as InventoryItem);
        }
        public bool Equals(InventoryItem obj)
        {
            return obj != null && obj.ID == ID;
        }
    }
and my inventory would have these attributes (WeaponItem inherits Item and ConsumableItemType inherits ItemType):

C#:
public class RPGInventory : InventoryBase
    {
        private Dictionary<InventoryItem, WeaponItem>[] m_AddedWeaponItems;
        private Dictionary<InventoryItem, int> m_InventoryItemsCount;
        private Item[] m_ActiveWeaponItem; //per slot
        private ConsumableItemType[] m_PouchConsumableItemType;
WeaponItem would have an inventoryItem attribute:
C#:
public class WeaponItem : Item
    {

        protected new WeaponItemType m_ItemType;
        [Tooltip("A reference to the object used to identify the item.")]
        [SerializeField] protected InventoryItem m_InventoryItem;

        public new WeaponItemType ItemType { get { m_ItemType = m_InventoryItem.ItemType as WeaponItemType; return m_ItemType; } }
        public InventoryItem InventoryItem { get { m_ItemType = m_InventoryItem.ItemType as WeaponItemType; return m_InventoryItem; } }
    }
But the problem is all the inventoryBase functions that are used all over the source code. There are too many functions that uses the ItemType as if it was a key to find an item. Here are a few (There are more):

C#:
public Item GetItem(int slotID, ItemType itemType) { return GetItemInternal(slotID, itemType); }

protected abstract Item GetItemInternal(int slotID, ItemType itemType);

public void EquipItem(ItemType itemType, int slotID, bool immediateEquip);

protected abstract Item EquipItemInternal(ItemType itemType, int slotID);

public void UnequipItem(ItemType itemType, int slotID);
    
public void UseItem(ItemType itemType, float count);

protected abstract void UseItemInternal(ItemType itemType, float count);

public void RemoveItem(ItemType itemType, int slotID, bool drop);

protected abstract Item RemoveItemTypeInternal(ItemType itemType, int slotID);
So I was wondering If it was even possible to do what I want without changing the source code for anything that was dealing with the inventory and items (Equip, pickup, useItem, etc..).

I understand that making the change yourself would cause a lot of problems for anyone who has created a lot of items in their game. But I think that this approach is very limiting.

What would you suggest I do? Change the source code for anything dealing with inventory and items or is there a way I haven't thought of? I am not going to create a new itemType for each "state" because it will be error prone, if ever need to change a base itemType, I would need to manually change all the others.

Changing the source code seems a bit extreme since the inventory base is used in a lot of places, but I really can't find an alternative.
 

Justin

Administrator
Staff member
#8
You'll still want to use ItemType as the key instead of switching to a new class. Within your inventory you can then map the ItemType to an InventoryItem.
 
#9
But that wouldn't work. For example if I have an ItemType called Iron Sword and my character has two weapons added: Iron sword Lv2 and Iron sword Lv4. How would I know which one to use, equip, get, remove, etc...?

I won't create an ItemType for each upgrade, because in the future if I would like to add more to the "state" of the itemType (durability, skills, etc..).
That means I would end up having hundreds of ItemTypes, which should really be only one.

I'm making an action RPG where you have to switch in between 3 weapons (they can be of the same itemType) during combos to chain them.

Do you have any other suggestion? I thought of an idea that might work, I would have an ItemCollection with all my itemTypes for my inventoryItems and another with itemTypes added to my character. I'm thinking I could procedurally create/remove "Instanced" itemTypes (they would have a reference to their inventoryItem match) from the second itemCollection when I add/remove them from my character. In my game I would always have at most 3 itemTypes in that second itemCollection. What do you think?
 
Last edited:

Justin

Administrator
Staff member
#10
Could you subclass ItemType and add any extra attributes to that subclass? You would then add these subclassed ItemTypes to your ItemCollection, and you wouldn't need to change any of the method definitions while still having the extra attributes.
 
#11
I am already doing that. I'll try to explain a bit better. I have an itemType subclass weaponItemType which has Attack and Magic power attributes. I want these attributes to be affected by my upgrade level. So I have an array int[] for both Attack and Magic power, each element of the array is for a different upgrade. Example Lv1 -> 10 attack, Lv3 -> 15 attack etc..

So now where do I store this upgrade lvl variable? If I have it in the ItemType then I will need to create the same itemType (example iron sword) for each upgrade with the only difference being the upgrade lvl (iron sword lv1, iron sword lv2). If I need to make changes to the iron sword, I will need to do it on all the iron sword variants. Imagine now I have more than just 5 upgrades, imagine I have upgrades, skills, experience, etc.. on my Items. There is no way I can maintain more than 100 itemType variants for every weapon in my game.

ItemTypes are scriptable objects, if I understand correctly I need to create them in the project view so that they can be referenced. The point here is to have unique ItemTypes. Instead, I would like to create/delete the ItemType variants I was talking about earlier, during the game. This way I can have my base itemType "iron sword" and I could create variant "iron sword lv3 with poison skill and critical boost" when I add it to my character. I can store the variant data in the InventoryItem class, while it is in the inventory and when I add it to the character I can create the itemType variant.

I'm thinking that if I add 3 times the same weapon to my character, "iron sword lv2", then there would be no way to differentiate them, except if I add another ID (instance id or variant id) to my new itemType subclass.

What do you think? The thing here is that I do not want to use the inspector or itemType manager to create 100 itemTypes that are variants of another "base" itemType.
 
#12
I too was very confused about the Item system when first learning UCC.
First of all, ItemType is a misnomer and it is more like a simple ItemID. Right now, you will need to create a unique ItemType all items.
I never fully understand why creating ScriptableObject is necessary for just an ID.

What I would like to see is to use simple ID (just int?) instead of ItemType and it will make things much simpler. You don't need to create new ScriptableObject. If you need extra info, there already is Item class and you can put them in there.

I'm too trying to extend UCC Item/Inventory system but I'm stuck between re-writing the whole thing(probably not a good idea) or build on top of UCC system(but it's not that easy to extend UCC without modifying UCC core codes).

However, there is an Asset called RuckSack, and RuckSack dev is trying to add UCC support and I decided to wait until how RuckSack will handle it. I'm sure he can do a better job than I.
But right now, he is stuck waiting for Justin to help. Without Justin's help, he cannot move forward(according to him)

Oh well,
Justin, as you mentioned, UCC Item system is very basic and you will have many users asking similar questions. If I were you, I'll help RuckSack to integrate soon and I'll point to RuckSack how to extend Item system. It will be then, RuckSack problem.
Unless you intend to write a comprehensive Item system, please help them to help you.

Thanks.
 

Justin

Administrator
Staff member
#13
I talked to @Sangemdoko in more detail about this and we have a solution that will work. I won't be able to implement it until at least version 2.2 or 2.3 but at least we now have a viable solution.
 
#15
How's it going with the "solution that will work"? I'm very interested in this.
Is there something I can take a look?
How did you solve the issue with many variants?
And I'm also curious how it will play with ItemSetManager where all of your game items has to be listed there. Instead of listing individual items in the ItemSetManager, perhaps, item categories such as knife, gun, sword, and such will make better sense??
 
#17
Oh, I miss understood that you and Sangemdoko have some interim solution that will work.
If that's the case, is it possible you to help answer RuckSack guy whatever that means...^^
Once you do, I'll push him to support UCC.

Thanks.
 
Top