UI Designer: Item Hotbar Cooldown and Drag/Drop Selection Preview

Stormi

Member
While I still need to add all the individual abilities, I found a way to fully utilize UIS for my ability system by using a combination of unique item categories, item actions and custom code.

The abilities are set up as items within their own categories. For each subcategory I have a custom item action that takes care of CanInvokeInternal() & InvokeActionInternal() and if needed hands over certain variables to a custom AbilityHandler (for example to start a coroutine which overrides the ready status of an ability item within UIS after the time specified within a cooldown attribute has passed).

Similar to the regular inventory I did set up another grid inventory with grid tabs that are limited to the ability item categories. The collection has the purpose "Secondary". All of this seems to be working just as I want to.

I`m currently just struggling a bit with the UI Designer to solve the three following issues that are somewhat related to each other:

1) While my Equipment Grid shows "red" when abilities (and regular items not suitable for a particular slot) are dragged over I do get a "green" feedback when trying to drag an ability item into the regular inventory and vice versa. They don't actually get dropped there due to the correct restrictions, but I cannot fully grasp where I need to change something to also have the highlights for red/green accordingly.

2) When dragging items somewhere, to be able to drop them (and to get the colored restriction feedback in the first place) the mouse cursor itself always needs to be exactly on the border of a particular slot. While some slots register it on multiple borders, many only have one of their four borders actually do something. Here is an example, same session, same item, same slot. Coming from left it shows red exactly when the mouse is on the left border and and would have been droppable if the right item for this slot. Coming from right, even when going further over the slot, still nothing is registered at all. This also applies to the case when I want to grab something within the slots.

1610305017413.png

3) The Item Hotbar correctly accepts both regular items as well as abilities (which is not surprising, considering both types within UIS simply are items). I couldn't find a proper solution though yet in regards to visualizing a cooldown. The easy way would be to simply override the icon of an item or ability that currently is not ready and then override it again, when the cooldown is over. I would prefer to not go this route though and instead have some kind of overlay like this:

1610305307930.png

Before investing even more time to figure out a solution for this, I just wanted to ask first, if the UI designer or UIS in general do provide anything that could be utilized for this purpose? It could after all also apply for regular items like potions etc.

Thanks a lot for any kind of support.
 
I'm glad you were able to make so much progress!

1)
The Red and Green color feedback is showed to you by the ItemView, precisely the Drop Hover Icon Preview Item View Module
1610352967849.png

The Condition to know whether the condition has passed or not is managed by the Drop handler.
Everything about this component is explained in this video: https://opsive.com/videos/?pid=22330&video=24159

You can check the Debug Passed Conidition field to know the index of the condition that passed, from there you can try to figure out why the condition passes and yet does not add the item when dropped.
1610353122212.png
The smart exchange condition and action should be enough for most use cases but sometimes you'll need to combine condition and actions or even write your own custom ones.

2)
That sounds like something is covering up your ItemView on the right side so it does not receive the hover event. Check online how to debug UI overlaying each other. The Event System usually tells you what gameobject is being hovered in the inspector. From there you can try to find what is preventing the ItemView from being hovered (could be a transparent image that overlaps from somewhere)

3)
That's a good question... I'm not completely sure how I would implement that, but it sounds like something a few people would want so I'll add it to my todo list.
I think I would create a new Item View Module with a coroutine or an update function which refreshes the radial overlay. The custom ItemAction could find the Item Hotbar, get the correct slot, find the Item View and trigger the start function on the Item View Module.
You'll need to take into account people that unequip and equip the item while it is in cooldown...
This might require some kind of cooldown Manager which could be part of a custom Item Hotbar (or can sit next to the hotbar)

I hope that helps :)
 
I needed the cooldown thing today (theres no numbers on it)

so i made this ....

just modify your prefab for your ItemViewForHotbar and put this component onto the root object


C#:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEditor.AnimatedValues;
using UnityEditor;
public class AbilityCooldown : MonoBehaviour
{
    // Start is called before the first frame update
    private float _overlayAmout;
    private Color _overlayColor = new Color(0, 0, 0, 0.75f);
    private float _cooldownTime;
    public float cooldownTime
    {
        get => _cooldownTime;
        set
        {
            _overlayAnimFloat = 0;
            _cooldownTime = value;
        }
    }
    public Color overlayColor
    {
        get => _overlayColor;
        set
        {
            if(overlayIm!=null){overlayIm.color = value;}
            _overlayColor = value;
        }
    }
    private float _overlayAnimFloat;
    public float overlayAmount
    {
        get
        {
            return _overlayAmout;
        }
        set
        {
            _overlayAmout = value;
            if (overlayIm != null)
            {
                overlayIm.fillAmount = _overlayAmout;
            }
        }
    }
    private Image overlayIm;
    void Start()
    {
        Transform image = transform.Find("Image");
        GameObject gameOb = Instantiate(image.gameObject, image.position, image.rotation, transform);
        Sprite fill = Sprite.Create(new Texture2D(1, 1), new Rect(0, 0, 1, 1), new Vector2(0.5f, 0.5f), 1, 0, SpriteMeshType.FullRect, new Vector4(0, 0, 0, 0), false);
        Image im = gameOb.GetComponent<Image>();
        GameObject text = new GameObject();
        TMPro.TextMeshPro m_Text = text.GetComponent<TMPro.TextMeshPro>() ?? text.AddComponent<TMPro.TextMeshPro>();
        text = Instantiate(text, image.position, image.rotation, gameOb.transform);
        // text.AddComponent(TMPro.TextMeshPro)
        im.sprite = fill;
        im.type = Image.Type.Filled;
        im.fillAmount = overlayAmount;
        im.color = overlayColor;
        overlayIm = im;
    }
    // Update is called once per frame
    void Update()
    {
        if (_cooldownTime > 0)
        {
            _overlayAnimFloat += Time.deltaTime / cooldownTime;
            overlayAmount = Mathf.Lerp(1, 0, _overlayAnimFloat);
            if (overlayAmount < 0.0001)
            {
                cooldownTime = 0;
            }
        }
    }
}
#if UNITY_EDITOR
//Editor Code and namespaces
[CustomEditor(typeof(AbilityCooldown))]
public class CustomInspector : Editor
{
    private float testCooldown = 2.5f;
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        AbilityCooldown ac = target as AbilityCooldown;
        ac.overlayColor = EditorGUILayout.ColorField("Overlay Color",ac.overlayColor);
        ac.overlayAmount = EditorGUILayout.Slider(ac.overlayAmount, 0, 1);
        EditorGUILayout.LabelField("Just set the .cooldownTime to a float to start the cooldown");
        EditorGUILayout.BeginHorizontal();
        testCooldown = EditorGUILayout.FloatField("Cooldown Seconds", testCooldown);
        if (GUILayout.Button("Test Cooldown"))
        {
            ac.cooldownTime = testCooldown;
        }
        EditorGUILayout.EndHorizontal();
    }
}
#endif

while you are running the code with items in the hotbar you can set a test cooldown and hit the button to see it in action
 
Last edited:
Very impressive @joran !

It's a bit unrelated to the question but I wanted to pointed out. You must encompass your Editor code within a
#if UNITY_EDITOR
//Editor Code and namespaces
#endif

Otherwise your project won't build.

I usually keep my editor and runtime code in different scripts such that they can be in different assemblies, but I understand the convinience of having the inspector code in the same file and the component
 
1) As always super input, thank you Santiago, I really appreciate it very much. I now created individual drop handlers for Equipment/Inventory, Abilities & Hotbar. By adding a "From Collection" condition on top of the smart exchange I was able to get the desired result, meaning a slot is highlighted in red when coming from a collection that is not allowed to drop something in wherever the mouse currently is. I am only struggling with some leftover issues here now.

The Item Hotbar is not a collection, so I cannot allow the drop handlers within equipment/inventory/abilities to accept something from the hotbar. In generel this is not a problem, because the items are after all never really removed from those collections. But I still need to be able to remove stuff from the item hotbar of course. Ideally I simply want a left click (or hotkey click) to use/execute whatever sits in a hotbar slot and a right click to remove it from the hotbar. Is that easily done? I'm not sure 100% how to get control over whatever is within the hotbar. The ItemAction "Remove" does after all not remove the reference within the hotbar but rather the item/stack within the collection itself.

2) THANKS! It was so obvious and right in front of me but I probably could have spent forever looking for it without ever finding the culprit => The silhouette image. The transparent part of it does overlap partially and covers the exact spots where the hover does not get activated. Now it was a 1sec fix, lol :)

3) Sounds great in regards to getting something cooldown related included within UIS eventually. I am sure many will be happy about it, because it, as said before, also can be used for items that are on cooldowns etc.

But of course also several "thanks a lot" @joran here for sharing his solution. I will definitively check it out as it looks great already :)
 
1) For the drop condition there should be a "Container Has Name" condition. Simply name your Item Hotbar "Item Hotbar" in the ItemHotbar inspector and then use that same name in the condition. This way you'll know whether the item was dropped from or to the Item Hotbar and you can deal with it however you like

If you want a right click to remove the Item from the hotbar you'll need to add a custom component that detects a click on the Item View Slot.
You could either override the ItemViewSlot OnPointerClick function or write a custom component that sits next to the ItemViewSlot and detects right clicks.
Here is how you detect right-clicks on a UI:

In the future I'll probably built-in the right click functionality within ItemViewSlot but I'm not quite sure how to best implement it yet so that it can be generic enough for everyone to use (or not).

Then to remove the item from the itemhotbar you can use (this does not remove the item from the invnentory):
Code:
itemHotbar.RemoveItem(itemInfo, slotIndex);
 
Worked like a charm (after a few tries) :D Thanks a lot, Santiago. I even used the itemHotbar.GetItemAt() you provided HERE. I basically ended up doing the following in case someone else want's to do the same / something similar:

  • I created a custom script "HotbarControl" and attached it to the parent object of the Item View Prefab used for Not-Empty Hotbar Slots
C#:
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using Opsive.UltimateInventorySystem.UI.Panels.Hotbar;
using Opsive.UltimateInventorySystem.UI.Item.ItemViewModules;

namespace RPG.Inventory
{
    public class HotbarControl : MonoBehaviour, IPointerClickHandler
    {
        private int slotIndex;
        private ItemHotbar itemHotbar;
        private SlotIndexView slotIndexView;

        public UnityEvent leftClick;
        public UnityEvent rightClick;

        private void Awake()
        {
            itemHotbar = GetComponentInParent<ItemHotbar>();
            slotIndexView = GetComponentInParent<SlotIndexView>();
        }

        private void Start()
        {
            slotIndex = slotIndexView.ViewSlot.Index;
        }

        public void OnPointerClick(PointerEventData eventData)
        {
            if (eventData.button == PointerEventData.InputButton.Right)
                rightClick.Invoke();

            if (eventData.button == PointerEventData.InputButton.Left)
                leftClick.Invoke();
        }

        public void UseFromHotbar()
        {
            itemHotbar.UseItem(slotIndex);
        }

        public void RemoveFromHotbar()
        {
            itemHotbar.RemoveItem(itemHotbar.GetItemAt(slotIndex), slotIndex);
        }
    }
}

  • Via the new component I added the "RemoveFromHotbar" method to be invoked on RightClick and "UseFromHotbar" for LeftClick.
  • Then I added an Event Trigger component without adding any methods on there (without the component, the clicks weren't being registered)
And that's it. Now I can drag stuff into the hotbar and remove it with right click without removing it from whatever collection they came from and I can use what is in the slot with leftClick. Also, I now have access to the ItemHotbar, the ItemInfo and the ItemIndex in one place, so it would not be any problem to also add events for middleClick or in other ways customize the usage of the hotbar even more.
 
Last edited:
Top