Change Character Attributes with (Skinned) Item

Klin

Member
Hi,

I have armor items. When I wear them, the character should have it's attributes changed (increasing health, armor etc.)

How would I accomplish that?

I tried to add an Item Object & Item Binding script to the item prefab.
My problem now is, how would I use the characters attributes in the Item Binding script? Would I need some kind of custom script which fires when the item is attached?
(With a weapon, as shown in the integration video, it's obvious, since that script is allready attached to the prefab).

Thanks for helping out!
 
There are many ways to go about this and they all require some custom component.

1)
Use a component that monitors your equipment Item Collection to know when items are equipped/unequipped and then recompute the stats for your character.
The reason we do not do this built-in is because character stats are affected by many things other then items. That being said the Equipper component used to equip your armor has a function that returns a combination of all the attribute with the same name on all equipment. So you can take advantage of that. Or you can create your own script for combining attribute values toghether.

Check out this page:

2)
Instead of having a script that monitors the entire item collection do it individually by item. I would not recommend that approach since your character Attributes won't be able to change

I hope that helps
 
Just in case someone lands here and is in search for a quick solution for picking up Skinned Items and udpating the UCC Attribute Manager health & shield values with Item Definition Attributes.

Splitted up because 10000 characters gap in the forum.

Code:
using System;
using System.Collections.Generic;
using UnityEngine;
using Opsive.Shared.Game;
using Opsive.Shared.Utility;
using Opsive.UltimateInventorySystem.Core;
using Opsive.UltimateInventorySystem.Equipping;
using Opsive.UltimateCharacterController.Traits;
using Opsive.UltimateInventorySystem.Core.DataStructures;
using Opsive.UltimateInventorySystem.Core.InventoryCollections;
using EventHandler = Opsive.Shared.Events.EventHandler;


    /// <summary>
    /// Handles equipping & unquipping of skinned items by updating the UCC AttributeManager & UIS Item Collection.
    /// </summary>
   
    /*
     * General:
     *
     * When an Item with the Skinned Item Category parent gets picked up,
     * it gets into the Skinned Item Collection when Health+Shield values together are bigger than that of current item.
     * Otherwise picked up item remains in Default Item Collection.
     * The Item that gets replaced will also be moved into Default Item Collection.
     *
     *
     *
     * HowTo:
     *
     * Attach this script to the character.
     *
     * Attach Equipper.cs to the character.
     *  -> Equipment Item Collection ID -> Name = m_SkinnedItemCollectionName
     *  -> Equipment Item Collection ID -> Purpose = None
     *  -> Main Root Node = Character root (Transform)
     *  -> Item Slot Set =  UIS ItemSlotSet specifically created for Skinned Items.
     *
     * In the Inventory Manager, create a parent Item Category for all Skinned Items.
     * Create one descendand Item Category for each root bone which has items on it.
     * List them in the Item Slot Set (1 for pelvis, 1 for upperarm etc.)
     *
     * Add the (Shield)Attribute to the Attribute Manager.
     * Add the Item Definition Attributes to the (parent) Item Categories in the Inventory Manager.
     *
     * The Skinned Item only needs an empty parent with 2 child components inside the prefab:
     *  -> The Skinned Mesh Renderer with the Mesh, Material and Root Bone defined.
     *  -> The root (Transform)
     * It is also possible to have multiple Items inside the prefab. Just add another 2 child components.
     *
     * For picking up, the "InventoryItemPickup" from the project folder (which was created with the Character) is used.
     * Under the "Inventory" component, the Item Collection is "m_SkinnedItemCollectionName" (same as in Equipper.cs).
     * Then under "Inventory -> Item Amounts" choose the Item Definition.
     *
     *
     *For Respawn:
     *Add CharacterRespawner.cs & in the Character Inventory Bridge, disable "Remove All On Death".
     *(Items don't get put into Loadout, add this function in this script in case needed)
     */
   
    public class SkinnedItemPickUp : MonoBehaviour
    {
        [Tooltip("The name of the Parent Item Category of the Skinned Items. Only items with this parent get handled by this script when picked up.")]
        [SerializeField] protected string m_SkinnedItemParentCategoryName = "SkinnedArmor";
        [Tooltip("The name of the ItemCollection to put the skinned items in.")]
        [SerializeField] protected string m_SkinnedItemCollectionName = "Skinned Armor Equipped";
        [Tooltip("The name of the ItemCollection that contains the items that are not equippable.")]
        [SerializeField] protected string m_DefaultItemCollectionName = "Default";
       
       
        [Tooltip("The name of the health attribute in the Attribute Manager & Item Definition Attribute. Will be used to compare with picked up item.")]
        [SerializeField] protected string m_AttributeHealth = "Health";
        [Tooltip("The name of the Item Definition Attribute to regenerate Health (heal).")]
        [SerializeField] protected string m_AttributeRegenerateHealth = "RegenerateHealth";
        [Tooltip("Maximum value at game start of the health attribute in the Attribute Manager.")]
        [HideInInspector][SerializeField] protected float m_AttributeMaxHealthValueBase;
       
        [Tooltip("The name of the shield attribute in the Attribute Manager & Item Definition Attribute. Will be used to compare with picked up item.")]
        [SerializeField] protected string m_AttributeShield = "Armor";
        [Tooltip("The name of the Item Definition Attribute to regenerate Shield.")]
        [SerializeField] protected string m_AttributeRegenerateShield = "RegenerateArmor";
        [Tooltip("Maximum value at game start of the armor attribute in the Attribute Manager.")]
        [HideInInspector][SerializeField] protected float m_AttributeMaxShieldValueBase;
       
       
        private ItemCollection m_SkinnedItemCollection;

        private ItemCollection m_DefaultItemCollection;

        private IEquipper m_Equipper;
       
        private AttributeManager _attributeManager;
       
        private Inventory m_Inventory;

       
        private void Awake()
        {

            _attributeManager = GetComponent<AttributeManager>();
            m_Equipper = GetComponent<IEquipper>();
            m_Inventory = gameObject.GetCachedComponent<Inventory>();
           
            m_SkinnedItemCollection = GetItemCollection(m_SkinnedItemCollectionName, true);
            m_DefaultItemCollection = GetItemCollection(m_DefaultItemCollectionName, true);

            //The attribute base values from the Attribute Manager at game start
            if (m_AttributeMaxHealthValueBase == 0)
            {
                m_AttributeMaxHealthValueBase = _attributeManager.GetAttribute(m_AttributeHealth).MaxValue;
            }
            if (m_AttributeMaxShieldValueBase == 0)
            {
                m_AttributeMaxShieldValueBase = _attributeManager.GetAttribute(m_AttributeShield).MaxValue;
            }
           
           
            if (m_Equipper != null) {
                EventHandler.RegisterEvent(m_Equipper, EventNames.c_Equipper_OnChange, OnItemEquipped);
            }
            if (m_Inventory != null)
            {
                EventHandler.RegisterEvent<ItemInfo, ItemStack>(m_Inventory,
                    EventNames.c_Inventory_OnAdd_ItemInfo_ItemStack, OnAddItemToInventory);
                EventHandler.RegisterEvent<ItemInfo>(m_Inventory, EventNames.c_Inventory_OnRemove_ItemInfo,
                    OnRemoveItemFromInventory);
            }

        }
       
       
        /// <summary>
        /// An Item has been added to the Inventory. Event also gets fired when Item is moved inside Inventory.
        /// </summary>
        /// <param name="addedItemInfo">The info that describes the item.</param>
        /// <param name="addedItemStack">The ItemCollection that the item was added to.</param>
        private void OnAddItemToInventory(ItemInfo addedItemInfo, ItemStack addedItemStack)
        {
            //Only continue if item has specified Skinned Item Category as parent.
            if (!CheckItemCategory(addedItemInfo, m_SkinnedItemParentCategoryName))
                return;

            // Check for this specific item (because event gets fired everytime item is moved to another collection).
             var checkItemInfoSkinned = m_SkinnedItemCollection.GetItemInfo(addedItemInfo.Item);
             if (checkItemInfoSkinned != null)
                 return;

             MoveItemToItemCollection(addedItemInfo);
           
            //Update current attributes.
            UpdateCurrentAttributeValues(addedItemInfo);
        }
 
Code:
        /// <summary>
        /// An Item has been removed from the Inventory.
        /// </summary>
        /// <param name="itemInfo">The info that describes the item.</param>
        private void OnRemoveItemFromInventory(ItemInfo itemInfo)
        {

        }

       
        /// <summary>
        /// An Item has been equipped.
        /// </summary>
        void OnItemEquipped()
        {
            UpdateMaximumAttributeValues();
        }

       
        /// <summary>
        /// Moves item to another Item Collection.
        /// </summary>
        void MoveItemToItemCollection(ItemInfo addedItemInfo)
        {
            //Using a pooled array instead of creating a new array each time.
            var pooledArray = GenericObjectPool.Get<ItemInfo[]>();
           
            //Return if item definition is allready in Skinned Item Collection.
            var currentItemDefinitionSkinned = m_SkinnedItemCollection.GetItemInfosWithDefinition(addedItemInfo.Item.ItemDefinition, ref pooledArray, true);
            if (currentItemDefinitionSkinned.Count > 0)
                return;
           
            var addedItemItemCategory = addedItemInfo.Item.Category;
            var addedItemItemCollection = addedItemInfo.Item.ItemCollection;

            //Get all items from Skinned Item Collection with the same category.
            var filterParameter = InventorySystemManager.GetItemCategory(addedItemItemCategory.name);
            var skinnedItemCollection = m_Inventory.GetItemCollection(m_SkinnedItemCollectionName);
            var currentItemInfoList = skinnedItemCollection.GetItemInfosWithCategory(filterParameter, ref pooledArray, true);
           
            addedItemInfo.Item.TryGetAttributeValue(m_AttributeHealth, out float addedItemCompareHealthValue);
            addedItemInfo.Item.TryGetAttributeValue(m_AttributeShield, out float addedItemCompareShieldValue);

            if (currentItemInfoList.Count == 0)
            {
                //Give Item to Skinned Item Collection.
                addedItemItemCollection.GiveItem(addedItemInfo, m_SkinnedItemCollection);

                //Return the pooled array.
                GenericObjectPool.Return(pooledArray);
               
                return;
            }

            for (int i = 0; i < currentItemInfoList.Count; i++)
            {
                currentItemInfoList[i].Item.TryGetAttributeValue(m_AttributeHealth, out float currentCompareHealthValue);
                currentItemInfoList[i].Item.TryGetAttributeValue(m_AttributeShield, out float currentCompareShieldValue);

                var differenceHealthValue = addedItemCompareHealthValue - currentCompareHealthValue;
                var differenceShieldValue = addedItemCompareShieldValue - currentCompareShieldValue;
                var differenceValue = differenceHealthValue + differenceShieldValue;
               
                if (differenceValue > 0)
                {
                    //Move current Item from Skinned Item Collection into default.
                    m_SkinnedItemCollection.RemoveItem(currentItemInfoList[i]);
                    //Give Item to Skinned Item Collection.
                    addedItemItemCollection.GiveItem(addedItemInfo, m_SkinnedItemCollection);
                    //Current item gets into Default Item Collection.
                    m_DefaultItemCollection.AddItem(currentItemInfoList[i]);
                   
                    //Return the pooled array.
                    GenericObjectPool.Return(pooledArray);
                   
                    return;
                }
            }
        }
       

        /// <summary>
        /// Check if Item has specified Item Category.
        /// </summary>
        bool CheckItemCategory(ItemInfo addedItemInfo, string ItemCategoryName)
        {
            var itemParents = addedItemInfo.Item.Category.GetDirectParents();
            for (int i = 0; i < itemParents.Count; i++)
            {
                if (itemParents[i].ToString() == ItemCategoryName)
                    return true;
            }
            return false;
        }
       
       
        /// <summary>
        /// Updates maximum values in the UCC Attribute Manager with the Item Definition Attributes.
        /// Always update total value to avoid multiple updates & rounding errors.
        /// </summary>
        void UpdateMaximumAttributeValues()
        {
            if (_attributeManager != null)
            {
                var attributeHealth = _attributeManager.GetAttribute(m_AttributeHealth);
                if (attributeHealth != null)
                {
                    attributeHealth.MaxValue =
                        m_AttributeMaxHealthValueBase + m_Equipper.GetEquipmentStatInt(m_AttributeHealth);
                }

                var attributeArmor = _attributeManager.GetAttribute(m_AttributeShield);
                if (attributeArmor != null)
                {
                    attributeArmor.MaxValue = m_AttributeMaxShieldValueBase +
                                              m_Equipper.GetEquipmentStatInt(m_AttributeShield);
                }
            }
        }


        /// <summary>
        /// Updates current values of health and shield in the Attribute Manager with the Item Definition Attributes.
        /// </summary>
        /// <param name="itemInfo">The info that describes the item.</param>
        void UpdateCurrentAttributeValues(ItemInfo itemInfo)
        {
            if (_attributeManager != null)
            {
                if (itemInfo.Item.TryGetAttributeValue(m_AttributeRegenerateHealth, out float regenerateHealthValue))
                {
                    var attribute = _attributeManager.GetAttribute(m_AttributeHealth);
                    if (attribute != null)
                    {
                        attribute.Value += regenerateHealthValue;
                    }
                }

                if (itemInfo.Item.TryGetAttributeValue(m_AttributeRegenerateShield, out float regenerateShieldValue))
                {
                    var attribute = _attributeManager.GetAttribute(m_AttributeShield);
                    if (attribute != null)
                    {
                        attribute.Value += regenerateShieldValue;
                    }
                }
            }
        }


        /// <summary>
        /// Returns ItemCollection with the specified name.
        /// </summary>
        /// <param name="collectionName">The name of the ItemCollection.</param>
        /// <param name="errorIfNull">Should an error be logged if the ItemCollection cannot be found?</param>
        /// <returns>The ItemCollection with the specified name.</returns>
        private ItemCollection GetItemCollection(string collectionName, bool errorIfNull)
        {
            var itemCollection = m_Inventory.GetItemCollection(collectionName);
            if (errorIfNull && itemCollection == null) {
                Debug.LogWarning($"Error: Unable to find the Item Collection with name {collectionName} within the Character Inventory.");
            }
            return itemCollection;
        }
       
       
        private void OnDestroy()
        {
            if (m_Equipper != null) {
                EventHandler.UnregisterEvent(m_Equipper, EventNames.c_Equipper_OnChange, OnItemEquipped);
            }
            if (m_Inventory != null)
            {
                EventHandler.UnregisterEvent<ItemInfo, ItemStack>(m_Inventory,
                    EventNames.c_Inventory_OnAdd_ItemInfo_ItemStack, OnAddItemToInventory);
                EventHandler.UnregisterEvent<ItemInfo>(m_Inventory, EventNames.c_Inventory_OnRemove_ItemInfo,
                    OnRemoveItemFromInventory);
            }
        }
    }
 
Last edited:
It looks good.
One little change I would make is use a "DynamicItemCategory" instead of the string item category name. Then you could remove the "CheckItemCategory" function and instead use the provided ItemCategory function:

Code:
var categoryMatch = m_EquipmentDynamicItemCategory.Value.InherentlyContains(itemInfo.Item);

DynamicItemCategory is a wrapper that automatically gets the ItemCategory from the database using the name provided, and it does it once per play through, it will be more performant than doing multiple string comparisions each time an item is equipped/unequipped.
 
Top