Best way to equip multiple of an equipment type?

I would really like my game will probably feature some sort of made up creature (as soon as I can find the right asset pack) as the main character. They will probably wear multiple rings or charms or similar rather than traditional weapon, armor, etc. But even with a human, games like Dragon Age for example allow the character to wear 2 rings. The dictionary seems incompatible with this. I also think that the “stackable” equipment type also would not be a great fit for this experience.

My idea was to create my own equipment script and more or less “hard code” it. e.g. 3 rings, 1 headgear piece, 2 charms. I think the interface methods would stay (almost) identical but the logic and backing store would just be different to accommodate what I want.

EDIT: I am thinking there will be some similarity of my Equipment with Inventory. An array of EquipmentSlot, which are each structs containing the EquipableItem and the EquipLocation. EquipmentSlotUI will be like InventorySlotUI in that it will have an index and map back to the Equipment script. None of the equipment slots will be stackable (though an individual Inventory item might be stackable in the inventory). It’s basically an inventory where each slot can only take a single type of item, but there may be multiple slots that can each take a common type.

Am I thinking about this straight?

I’ve played around with this idea before.

You want to maintain a fixed amount of slots for some items, and flexibilty for others… Here’s one idea that might work:

        [SerializeField] EquipLocation[] slotConfiguration;
        private Dictionary<int, EquipableItem> slots;

In the Equipment component (StatsEquipment) on the player, we would need to assign each EquipLocation to each available slot. This will allow you to have multiple EquipSlot.Ring slots.

EquipSlotUI would be changed to take in an int instead of an EquipLocation.

When checking to see if an item can be added to a slot, you would check the slotConfiguration[slot] to get the allowed Equip location.

2 Likes

Thanks. I’m curious why you opted for a Dictionary with int’s as keys instead of an array to parallel the slotConfiguration array. It seems Dictionary would be great if you have a wide range of non-sequential int’s (which is not the case here). Curious if they’re some other benefit I’m missing or if it’s just 6 of one and 1/2 dozen of the other.

Mostly reducing the volume of changes required to the system.

Cool thanks. I am in the midst of the ActionItem stuff now and seeing we use a Dictionary there too with int keys. I can see some value in having consistency.

In my rewrite of the Inventory, I actually use a Dictionary<int, InventorySlot> as well. I’m very fond of Dictionaries.

1 Like

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Can you please lead me through the changes I need to make to implement this idea? I also wanted to use two slots of the same type(e.g. weapons) but the current system duplicates a single item and fills both slots of the same type with it.

The answer I used for this question was theoretical, sort of off the cuff… it might be a few days, but I’ll see if i can flesh it out into something more useable.

1 Like

Thank you, any help is appreciated. As I stated in another post regarding multiple characters (with the risk of being redundant), i got the same duplication problem so hopefully one solution will tackle both problems.

I finally found some time to expand on this idea, though it doesn’t fully deal with things like multiple weapons…
First, made some changes to Equipment.cs. The backing field was changed to a Dictionary<int, EquipableItem> and nearly all of the methods were changed to accomodate this shift in the field.
Note that if you’ve not completed Shops and Abilities or Dialogues and Quests,then some lines won’t compile. I’ll comment those

Equipment.cs
using System;
using System.Collections.Generic;
using UnityEngine;
using GameDevTV.Saving;
using GameDevTV.Utils;

namespace GameDevTV.Inventories
{
    /// <summary>
    /// Provides a store for the items equipped to a player. Items are stored by
    /// their equip locations.
    /// 
    /// This component should be placed on the GameObject tagged "Player".
    /// </summary>
    public class Equipment : MonoBehaviour, ISaveable, IPredicateEvaluator
    {
        
        // STATE
        Dictionary<int, EquipableItem> equippedItems = new Dictionary<int, EquipableItem>();

        // PUBLIC

        /// <summary>
        /// Broadcasts when the items in the slots are added/removed.
        /// </summary>
        public event Action equipmentUpdated;

        /// <summary>
        /// Return the item in the given slot
        /// </summary>
        public EquipableItem GetItemInSlot(int slot)
        {
            if (!equippedItems.ContainsKey(slot))
            {
                return null;
            }

            return equippedItems[slot];
        }

        /// <summary>
        /// Add an item to the given slot. 
        /// </summary>
        public void AddItem(int slot, EquipableItem item)
        {
            //This line won't compile without Shops and Abilities course code
            Debug.Assert(item.CanEquip(item.GetAllowedEquipLocation(), this));

            equippedItems[slot] = item;

            if (equipmentUpdated != null)
            {
                equipmentUpdated();
            }
        }

        /// <summary>
        /// Remove the item for the given slot.
        /// </summary>
        public void RemoveItem(int slot)
        {
            equippedItems.Remove(slot);
            if (equipmentUpdated != null)
            {
                equipmentUpdated();
            }
        }

        /// <summary>
        /// Enumerate through all the slots that currently contain items.
        /// </summary>
        public IEnumerable<int> GetAllPopulatedSlots()
        {
            return equippedItems.Keys;
        }

        // PRIVATE

        object ISaveable.CaptureState()
        {
            var equippedItemsForSerialization = new Dictionary<int, string>();
            foreach (var pair in equippedItems)
            {
                equippedItemsForSerialization[pair.Key] = pair.Value.GetItemID();
            }
            return equippedItemsForSerialization;
        }

        void ISaveable.RestoreState(object state)
        {
            equippedItems = new Dictionary<int, EquipableItem>();

            var equippedItemsForSerialization = (Dictionary<int, string>)state;

            foreach (var pair in equippedItemsForSerialization)
            {
                var item = (EquipableItem)InventoryItem.GetFromID(pair.Value);
                if (item != null)
                {
                    equippedItems[pair.Key] = item;
                }
            }

            equipmentUpdated?.Invoke();
        }
        //This method won't compile without Dialogues and Quests code
        public bool? Evaluate(string predicate, string[] parameters)
        {
            if (predicate == "HasItemEquiped")
            {
                foreach (var item in equippedItems.Values)
                {
                    if (item.GetItemID() == parameters[0])
                    {
                        return true;
                    }
                }
                return false;
            }
            return null;
        }
    }
}

Next, I modified the EquipmentSlotUI.cs. Here, each slot gets a list of allowed equip locations, as well as an int representing the physical slot. Only an item with an AllowedEquipLocation within the list can be equipped.

EquipmentSlotUI.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using GameDevTV.Core.UI.Dragging;
using GameDevTV.Inventories;

namespace GameDevTV.UI.Inventories
{
    /// <summary>
    /// An slot for the players equipment.
    /// </summary>
    public class EquipmentSlotUI : MonoBehaviour, IItemHolder, IDragContainer<InventoryItem>
    {
        // CONFIG DATA

        [SerializeField] InventoryItemIcon icon = null;
        [SerializeField] int slot = -1;
        [SerializeField] private List<EquipLocation> allowedEquipLocations = new List<EquipLocation>();
        // CACHE
        Equipment playerEquipment;

        // LIFECYCLE METHODS
       
        private void Awake() 
        {
            var player = GameObject.FindGameObjectWithTag("Player");
            playerEquipment = player.GetComponent<Equipment>();
            playerEquipment.equipmentUpdated += RedrawUI;
        }

        private void Start() 
        {
            RedrawUI();
        }

        // PUBLIC

        public int MaxAcceptable(InventoryItem item)
        {
            EquipableItem equipableItem = item as EquipableItem;
            if (equipableItem == null) return 0;
            if (!allowedEquipLocations.Contains(equipableItem.GetAllowedEquipLocation())) return 0;
            if (!equipableItem.CanEquip(equipableItem.GetAllowedEquipLocation(), playerEquipment)) return 0;
            if (GetItem() != null) return 0;

            return 1;
        }

        public void AddItems(InventoryItem item, int number)
        {
            playerEquipment.AddItem(slot, (EquipableItem) item);
        }

        public InventoryItem GetItem()
        {
            return playerEquipment.GetItemInSlot(slot);
        }

        public int GetNumber()
        {
            if (GetItem() != null)
            {
                return 1;
            }
            else
            {
                return 0;
            }
        }

        public void RemoveItems(int number)
        {
            playerEquipment.RemoveItem(slot);
        }

        // PRIVATE

        void RedrawUI()
        {
            icon.SetItem(playerEquipment.GetItemInSlot(slot));
        }
    }
}

One final change was in Fighter’s UpdateWeapon method. Since the slots are ints instead of Equiplocations, we have to go through each slot to determine if one is a weapon. Note that this isn’t at all set up to handle multiple weapons, and will only handle the first weapon encountered.

        private void UpdateWeapon()
        {
            WeaponConfig foundWeapon = null;
            foreach (int slot in equipment.GetAllPopulatedSlots())
            {
                if (equipment.GetItemInSlot(slot) is WeaponConfig weaponConfig)
                {
                    foundWeapon = weaponConfig;
                    break;
                }
            }

            if (foundWeapon == null)
            {
                EquipWeapon(defaultWeapon);
            }
            else
            {
                EquipWeapon(foundWeapon);
            }
        }
1 Like

Thank you very much for your help! I will implement it and see how it fits my needs. Is the solution similar to the ActionSlotUI with the indexes? What I did with my Action Bar is that I added allowed ammo types and it worked neatly so I was wondering if doing the same index differentiation with the Equipment Slots will work. I don’t recall seeing UpdateWeapon(), hopefully it is something i don’t need or use at the moment. EDIT: I did some testing - works perfectly in my project, thank you again!

UpdateWeapon() is part of the Fighter component and mentioned in the integration section.

1 Like

Privacy & Terms