Getting Item informations when performing Action

Johnny Mendez

New member
Hello Santiago, hope you're doing well.

I am starting coding one of the most important thing of my game, which is the Item and Weapon managment in game (Not only in the inventory). So The target is to use UIS to get all the information I need in order to perform :
  • Item change (look)
  • Item Attributes (Is the weapon fire type, what is the attack, is the weapon giving special power to player etc.)
  • Item restriction (Can I euip this item if my level is under 20 for exemple)

So, I need to understand some things about the Event system and restriction system.
What I'm trying to achieve now is :
- As soon as I equip or unequip an item, I would like to
  1. Change the sprite of the right player part (Hat, Cape or Weapon)
  2. Modify some value regarding the item (If the item gives 20 LifePoint, then I will add 20 lifepoint to the player)
In Short, I'll need to access every Attribute of the item I just equipped. (Can be anything)


So for doing So, I was just tring to do a really simple test. I hadded an "Hat" script to the Hat Game Object and wanted to change the sprite of this GameObject with the ittem I equip.

You'll find in attachment all the informations.

Another thing I need to achieve is the Item restriction. I would like some item to be equippale only if some condition are met.
The most simple exemple would be the level of the player. I can only equip this Hat of player is level 10 or higher.
How can I achieve this ?

Have a nice day. Thank you a lot for your help
 

Attachments

  • UIS_Events.pdf
    257.9 KB · Views: 2
Thank you for the detailed question.

From the information you are telling I am assuming you are not using the "Equipper" component right?
Currently that script is quite limited and I often tell users that it is worth creating their own from scratch.

Right now you want to swap the hat sprite, but I assume you'll want to do the same for other things on the character like the weapon.

So my advice is to create a new component that would sit next to the character inventory. You can call the script "CharacterEquipper" or something like that. You'll use that component to listen to the inventory to know when an item was moved to the Equipped Item Collection.

The code example below should give you a good start. It is inspired from the Equipper script, make sure to check that one out if you have more questions. Also I added a lot of comments in the code to guide you, make sure to read them.

C#:
public class CharacterEquipper : MonoBehaviour //Optional Inherit IEquipper, if you do check the Equipper script.
{
    [Tooltip("The equippers inventory.")]
    [SerializeField] protected Inventory m_Inventory;
    [Tooltip("The equippers itemCollection within the inventory.")]
    [SerializeField]
    protected ItemCollectionID m_EquipmentItemCollectionID =
        new ItemCollectionID("Equipped", ItemCollectionPurpose.Equipped);
    
    protected ItemSlotCollection m_EquipmentItemCollection;
    
    /// <summary>
    /// Initialize the Equipper.
    /// </summary>
    protected virtual void Start()
    {
        if (m_Inventory == null) { m_Inventory = GetComponent<Inventory>(); }
        m_EquipmentItemCollection = m_Inventory.GetItemCollection(m_EquipmentItemCollectionID) as ItemSlotCollection;

        if (m_EquipmentItemCollection == null) {
            Debug.LogWarning("Your inventory does not have an equipment Item Collection.");
            return;
        }

        // The events needs to be declared exactly as specified, the event target and the parameter must match perfectly.
        EventHandler.RegisterEvent<ItemInfo, ItemStack>(m_Inventory, EventNames.c_Inventory_OnAdd_ItemInfo_ItemStack, OnAddedItemToInventory);
        EventHandler.RegisterEvent<ItemInfo>(m_Inventory, EventNames.c_Inventory_OnRemove_ItemInfo, OnRemovedItemFromInventory);

        var equipmentItemAmounts = m_EquipmentItemCollection.GetAllItemStacks();
        if (equipmentItemAmounts == null) {
            Debug.LogWarning("The Equipment Item Collection is null.");
            return;
        }
        for (int i = 0; i < equipmentItemAmounts.Count; i++) {
            Equip(equipmentItemAmounts[i].Item);
        }
    }
    
    /// <summary>
    /// Make sure to unregister the listener on Destroy.
    /// </summary>
    private void OnDestroy()
    {
        EventHandler.UnregisterEvent<ItemInfo, ItemStack>(m_Inventory, EventNames.c_Inventory_OnAdd_ItemInfo_ItemStack, OnAddedItemToInventory);
        EventHandler.UnregisterEvent<ItemInfo>(m_Inventory, EventNames.c_Inventory_OnRemove_ItemInfo, OnRemovedItemFromInventory);
    }

    /// <summary>
    /// Equip item that was added to the equipment collection.
    /// </summary>
    /// <param name="originItemInfo">The origin Item info.</param>
    /// /// <param name="addedItemStack">The added item stack.</param>
    private void OnAddedItemToInventory(ItemInfo originItemInfo, ItemStack addedItemStack)
    {
        if (addedItemStack == null) { return; }
        if (addedItemStack.ItemCollection == m_EquipmentItemCollection) {
            Equip(addedItemStack.Item);
        }

    }

    /// <summary>
    /// Unequip an item that was removed from the equipment collection.
    /// </summary>
    /// <param name="removedItemInfo">The removed Item info.</param>
    private void OnRemovedItemFromInventory(ItemInfo removedItemInfo)
    {
        if (removedItemInfo.ItemCollection == m_EquipmentItemCollection) {
            UnEquip(removedItemInfo.Item);
        }
    }
    
    /// <summary>
    /// Equip an item.
    /// </summary>
    /// <param name="item">The item to equip.</param>
    /// <returns>Return true only if the item equipped successfully.</returns>
    public virtual void Equip(Item item)
    {
        // Visually Equip your item!

        //Get the slot where the item was equipped by index:
        var itemSlotIndex = m_EquipmentItemCollection.GetItemSlotIndex(item);
        
        //Knowing the item slot index you can spawn the item in the right place

        //Use attributes to get the item sprite and other values that can affect the player

        if (item.TryGetAttributeValue("MyItemSprite", out Sprite sprite) == false) {
            //The item attribute does not exist!
            Debug.LogWarning("The attribute does not exist");
            return;
        }

        //Use the 'sprite' variable to swap the Sprite.
        //Do the same to get attack, health boosts, etc...
        
        //You can even send an event from here so that other objects can listen.
    }
    
    /// <summary>
    /// UnEquip an item.
    /// </summary>
    /// <param name="item">The item to unequip.</param>
    public virtual void UnEquip(Item item)
    {
        // Visually Unequip Item!

        //You can even send an event from here so that other objects can listen.
    }
}

For your other question to restrict the items you can do so using a IItemRestriction, you can find the documenation for that here:

Essentially make a Monobehaviour that inherits that interface. In the AddCondition function check the item Attribute or category to know if the item is allowed to equip the items.

You could actually use the same "CharacterEquipper" script and just inherit the interface.


C#:
public class CharacterEquipper : MonoBehaviour, IItemRestriction //Make sure to inherit the IItemRestriction
{
    //... The code from before

    public void Initialize(IInventory inventory, bool force)
    {
        //Nothing to do here if you don't want to.
    }

    /// <summary>
    /// Condition to add an item.
    /// </summary>
    /// <param name="itemInfo">The item Info that wants to be added.</param>
    /// <param name="receivingCollection">The collection that will receive the item.</param>
    /// <returns>The item info</returns>
    public ItemInfo? AddCondition(ItemInfo itemInfo, ItemCollection receivingCollection)
    {
        // If we aren't adding to equipped then let the item go through
        if (receivingCollection != m_EquipmentItemCollection) { return itemInfo; }

        //Check the item attribute to know if it can be equipped
        if (itemInfo.Item.TryGetAttributeValue("LevelRequired", out int itemLevel) == false) {
            
            //Attribute does not exist meaning no level requirement so equip the item no problem.
            
            return itemInfo;
        }
        
        //The item can be equipped fine if the character has a higher level
        //if(character.level > itemLevel) { return itemInfo; }

        //If not then the item cannot be equipped, therefore retrun null;
        return null;
    }

    /// <summary>
    /// No restriction on removing items.
    /// </summary>
    /// <param name="itemInfo">The item info to remove.</param>
    /// <returns>The item info to remove.</returns>
    public ItemInfo? RemoveCondition(ItemInfo itemInfo)
    {
        return itemInfo;
    }
}

To learn more about getting values from an item check this: https://opsive.com/support/documentation/ultimate-inventory-system/item/

I hope this helps, you should have everything to equip your item however you want, with control over what items get equipped.

If you find that you are limited by these options, and you want to have absolute control over your items equipping you can look into making a custom Item Collection, but that is more advanced, so only go for it if the options I gave you above aren't flexible enough
 
How exactly is it possible to get the attributes of currently equipped items? No matter what I try with Equipper or Inventory System Manager I don't get to a point that I can reference the attribute values of equipped items and thus cannot use those values in all my other scripts.
 
I'm sorry about this, I was convinced the function already existed, but it turns out the only way to get the the equipped items was by index. You can get the index from the ItemSlotSet but having a function that does that within the Equipper make more sense.
I've added a function to get the equipped item by slot name. It'll be available in the next update.

In the mean tiem add this to the Equipper script:
Code:
/// <summary>
        /// Get the item equipped in the slot provided.
        /// </summary>
        /// <param name="slotName">The slot name.</param>
        /// <returns>The item equipped in that slot.</returns>
        public virtual Item GetEquippedItem(string slotName)
        {
            var slotIndex = m_ItemSlotSet.GetIndexOf(slotName);
            if (slotIndex == -1) {
                return null;
            }

            return GetEquippedItem(slotIndex);
        }

Then you can get the equipped item from anywhere in the code using the inventory identifier (that's a component that sits next to your inventory)
From there you can get the equipper and therefore the equipped item (using the new function above).
Code:
var playerInventory = InventorySystemManager.GetInventoryIdentifier(1).Inventory;
var playerEquipper = playerInventory.gameObject.GetCachedComponent<Equipper>();
var equippedItem = playerEquipper.GetEquippedItem("RightHand");

EDIT: if you are interested to know the attribute of multiple equipped items you may use the GetEquippedStat(attributeName) frunction of the Equipper, it returns an int.
I changed the code such that now you may either get an int or a float. The change will be available in the next update.

Here it is if you wish to use it:
Code:
        /// <summary>
        /// Get the Equipment stats by retrieving the total value of the attribute.
        /// </summary>
        /// <param name="attributeName">The attribute name.</param>
        /// <returns>The total amount for the attribute.</returns>
        public virtual int GetEquipmentStatInt(string attributeName)
        {
            return (int)GetEquipmentStatFloat(attributeName);
        }
        
        /// <summary>
        /// Get the Equipment stats by retrieving the total value of the attribute.
        /// </summary>
        /// <param name="attributeName">The attribute name.</param>
        /// <returns>The total amount for the attribute.</returns>
        public virtual float GetEquipmentStatFloat(string attributeName)
        {
            var stat = 0f;
            for (int i = 0; i < m_Slots.Length; i++) {
                if (m_Slots[i].ItemObject == null) { continue; }
                var item = m_Slots[i].ItemObject.Item;

                if (item.TryGetAttributeValue<int>(attributeName, out var intAttributeValue)) {
                    stat += intAttributeValue;
                }
                if (item.TryGetAttributeValue<float>(attributeName, out var floatAttributeValue)) {
                    stat += floatAttributeValue;
                }
            }

            return stat;
        }
 
Last edited:
I tried your suggestions, Santiago, but I only got so far. For testing purposes I have this in Awake()

C#:
var playerInventory = InventorySystemManager.GetInventoryIdentifier(1).Inventory;
var equipper = playerInventory.gameObject.GetCachedComponent<Equipper>();
var equippedItem = equipper.GetEquippedItem("Head");
Debug.Log(equippedItem);

I verified that the itemslot is retrieved correctly but it always returns NULL. The part that is affected by whatever causes the issue is here:

C#:
        public virtual Item GetEquippedItem(int index)
        {
            Debug.Log(m_Slots[index].ItemObject); // only for testing!
            if (m_Slots[index].ItemObject == null) { return null; }
            return m_Slots[index].ItemObject.Item;

        }

It doesn't matter if an item is equipped or not, m_Slots[index].ItemObject is always NULL. It also doesn't matter if I try to directly use "GetEquippedItem(int index)" with the correct Item Slot Index instead of going through the string variant first. Here is an overview over the settings and prefabs:

item_issues.jpg

The first two images show inventory and equipper during runtime. For both slots an Item Object has been correctly added. Still - m_Slots[index].ItemObject returns NULL. The third image is a direct copy of the SwordUsableWeapon Prefab and the last two images are the EquipmentPrefabs for both items.

I am not sure why it returns NULL here for the m_Slots[index].ItemObject. I did debug the steps before too and it does get the right slot index 0 for the Head and 5 for the MainHand.

Also, as a related question that might help me understand everything better - why does the SwordUsableWeapon Prefab have values for cool down etc. in the melee attack component? Why are these not simply attributes of the item definition and could then be fetched with all other attribute values?

Thanks a lot for any help here.
 
Last edited:
Are you getting any errors/warnings in the console?

Everything seems fine. You can even see that the ItemObject field is set in the inspector, so it does not make sense that it is null in code...
perhaps the ItemSlotSet index and the "m_Slots" index don't match... but that doesn't make sense either...

Just in case, I added this new function to the Equipper class:
Code:
        /// <summary>
        /// Get the item Object slot by name.
        /// </summary>
        /// <param name="slotName">The slot name.</param>
        /// <returns>The item Object slot.</returns>
        public virtual ItemObjectSlot GetItemObjectSlot(string slotName)
        {
            for (int i = 0; i < m_Slots.Length; i++) {
                if (m_Slots[i].Name == slotName) {
                    return m_Slots[i];
                }
            }

            return null;
        }
Try to get the ItemObject from there to see if that works... but really it should give you the same result as before.

Is it possible that the items aren't yet equipped when get the equipped items? Try waiting a few frames or use a ContextMenu function to trigger the function from the inspector this way you can make sure it is equipped before you check the equipped item through code.


As for your other question, Melee Attack is just an example script. I didn't want to overpopulate the demo items with a ton of attributes, that's all.
Of course you could create your own Melee Attack component and make the cooldown (an any other field) an attribute.
With the ItemBinding component it is super easy to bind attribute values to properties on any component on the same gameobject as an ItemObject.
Check the docuementation to learn more about ItemBinding.
Of course if you are more comfortable with coding you can do the same and more by getting the attribute through code and setting the properties manually.
 
It was indeed too early apparently. I am not sure why, as the execution order of everything from what I could see shouldn't interfer here, but when I for test purposes did throw the same code into update - even the first log entry referenced the helmet already, so apparently it simply has to be fetched slightly later. I will mess around a bit and see where it can be positioned best but bottom line is - the functions available do give the option to get a hold of the items as desired. So thank you for that :)

In regards to the melee attack, thank you as well for the insights. It sounds as if I can do exactly what I want to => Throw as much if not all item related data as attributes on the item within UIS and then get a hold of it during runtime, extract the individual attributes and use their values in custom code, just as if the data was stored in self-made scriptable objects.
 
Top