通用窗口类 Inventory Pro 2.1.2 Demo1(下续篇 ),物品消耗扇形显示功能

本篇想总结的是Inventory Pro中通用窗口的具体实现,但还是要强调下该插件的重点还是装备系统而不是通用窗口系统,所以这里提到的通用窗口类其实是通用装备窗口类(其实该插件中也有非装备窗口比如NoticeUI等)。

本篇涉及的功能用加出标出,具体的功能如下:

1、实现了两个窗口,通过点击键盘I来,打开或者关闭窗口也就是Toggle功能

2、装备窗口中的物品栏空格数量动态生成可控,可以在属性窗口手动配置

3、窗口具有拖拽功能

4、窗口物品具有拖拽,及窗口间拖拽

5、可以在窗口使用物品的功能,物品有消耗扇形显示功能

6、通用窗口的类体系结构

这里开动之前给自己提几个问题:

1、UGui原生实现使用物品扇形消耗效果(即冷却实现)是如何实现的?

这个比较简单,UGUI图片本身就有这种雷达效果的遮罩,这篇文章讲的很详细,这里就不用说了

[入门教程] 用UGUI实现技能冷却效果

2、装备格子是如何与1所提到的方法接合在一起的?

这里回忆下装备格子是用InventoryUItemWrapper这个UI类实现的,所以答案就在这个类里,但使用这个类并非易事,它是用ItemCollectionBase类(集合容器)动态生成的,在前篇讲过这里再温习一下,这里我们发现如果没有值得话,它取的InventorySettingsManager.itemButtonPrefab,也就是个装备格子预设

        protected virtual void FillUI()
        {
            if (manuallyDefineCollection == false)
            {
                items = new InventoryUIItemWrapperBase[initialCollectionSize];

                // Fill the container on startup, can add / remove later on
                for (uint i = 0; i < initialCollectionSize; i++)
                {
                    items[i] = CreateUIItem<InventoryUIItemWrapper>(i, itemButtonPrefab != null ? itemButtonPrefab : InventorySettingsManager.instance.itemButtonPrefab);
                }
            }
            else
            {
                for (uint i = 0; i < items.Length; i++)
                {
                    items[i].itemCollection = this;
                    items[i].index = i;
                }
            }
        }

        protected T CreateUIItem<T>(uint i, GameObject prefab) where T : InventoryUIItemWrapperBase
        {
            T item = GameObject.Instantiate<GameObject>(prefab).GetComponent<T>();
            item.transform.SetParent(container);
            item.transform.localPosition = new Vector3(item.transform.localPosition.x, item.transform.localPosition.y, 0.0f);
            item.itemCollection = this;
            item.transform.localScale = Vector3.one;
            item.index = i;
        
            return item;
        }

回忆一下这个配置是一个必填配置,必须在Setting中进行设置,

setting

找到该预设,找到真相

c2_thumb1

c1_thumb3

看的出来这个预设绑定了InventoryUIItemWrapper类,其公共的Field也以此列了出来分别是Amout Text物品数量,Item Name物品名称(None),Icon(Image)这就是物品的图标了比如苹果,Cooldown Image 就是图片表面的遮罩层,用来做雷达效果的。

3、装备格子是如何与苹果等可以吃的动态物品接合在一起的?

从上面的图片我们也看的出来,其实默认的Icon应该是装备格子的背景(黑底),它是如何变成苹果梨,或者刀剑的呢?这里比较简单的线索就是通过拾取地上的包裹,然后在背包里多了一个物品(这个过程不表述了比较复杂,留在以后专门分析),顺藤摸瓜,最终还是要加入到背包里面,这样我们去看看ItemCollectionBase类中的AddItem方法,这里这个方法本身很复杂主要是有类似20个血瓶1打东西放置的时候需要重新计算格子什么的比较麻烦,核心的函数是SetItem

        /// <summary>
        /// This function can be overridden to add custom behavior whenever an object is placed in the inventory.
        /// This happens when 2 items are swapped, items are merged, anytime an object is put in a slot.
        /// <b>Does not handle repainting</b>
        /// </summary>
        /// <param name="slot"></param>
        /// <param name="item"></param>
        /// <returns>Returns true if the item was set, false if not.</returns>
        public virtual bool SetItem(uint slot, InventoryItemBase item)
        {
            if (CanSetItem(slot, item) == false)
                return false;

            // _item ugly work around, but no other way to keep it safe...
            items[slot].item = item;
            return true;
        }

这里我们还记得InventoryUIItemWrapper是Item的包装,所以这里设置了其的Item为新增的Item,顺着这个思路剩下相关的就是绘制部分了,再去看看InventoryUIItemWrapper的绘制部分看看是如何显示的以及冷却效果是如何实现的

        public override void Repaint()
        {
            if (item != null)
            {
                if (amountText != null)
                {
                    // Only show when we have more then 1 item.
                    if (item.currentStackSize > 1)
                        amountText.text = item.currentStackSize.ToString();
                    else
                        amountText.text = string.Empty;
                }

                if (itemName != null)
                    itemName.text = item.name;

                if(icon != null)
                    icon.sprite = item.icon;
            }
            else
            {
                if (amountText != null)
                    amountText.text = string.Empty;

                if (itemName != null)
                    itemName.text = string.Empty;

                if(icon != null)
                    icon.sprite = startIcon != null ? startIcon : InventorySettingsManager.instance.defaultSlotIcon;
            }

            //RepaintCooldown(); // Already called by update loop
        }
icon.sprite = item.icon; 这一行代码我们看到了,其实icon这个Image Field 对应Item.icon,而且是一个sprite对象(原来sprite是Image的一个部分),再看下Update Loop的代码,只是在每一帧调用了
RepaintCooldown(),也就是执行冷却刷新,具体代码如下:
        public virtual void RepaintCooldown()
        {
            if (cooldownImage == null)
                return;

            if (item != null)
            {
                if(item.isInCooldown)
                {
                    cooldownImage.fillAmount = 1.0f - item.cooldownFactor;
                    return;
                }
            }

            // To avoid GC
            if (cooldownImage.fillAmount != 0.0f)
                cooldownImage.fillAmount = 0.0f;
        }

当然这里有控制cooldown的逻辑,isInCooldown是一个属性逻辑都在里面,包括同类型物品使用的冷却控制(有点复杂,这里先不表了)

4、物品是如何被触发使用的?

触发使用一定是在点击的时候触发的,接合UGui的事件机制,Inventory Pro 分别在OnPointerDown和OnPointerUP中进行了实现,有一些触控相关的判断见一下源码

        public virtual void OnPointerDown(PointerEventData eventData)
        {
            if (itemCollection == null)
                return;

            pointerDownOnUIElement = InventoryUIUtility.clickedUIElement;
            if (pointerDownOnUIElement == false)
                return;

            if (InventorySettingsManager.instance.useContextMenu && (eventData.button == InventorySettingsManager.instance.triggerContextMenuButton || Application.isMobilePlatform))
            {
                if (item != null)
                    TriggerContextMenu();

                return;
            }

            if (InventorySettingsManager.instance.mobileUnstackItemKey == MobileUIActions.SingleTap)
            {
                TriggerUnstack();
                return;
            }
            else if(InventorySettingsManager.instance.mobileUseItemButton == MobileUIActions.SingleTap)
            {
                TriggerUse();
                return;
            }

            if (item != null && pressing == false && Time.timeSinceLevelLoad - InventorySettingsManager.instance.mobileDoubleTapTime < lastDownTime)
            {
                // Did double tap
                if (InventorySettingsManager.instance.mobileUnstackItemKey == MobileUIActions.DoubleTap)
                {
                    TriggerUnstack();
                    return;
                }
                else if(InventorySettingsManager.instance.mobileUseItemButton == MobileUIActions.DoubleTap)
                {
                    TriggerUse();
                    return;
                }
            }

            lastDownTime = Time.timeSinceLevelLoad;
            pressing = true;
        }

物品的具体使用在TriggerUse() 实现

        public override void TriggerUse()
        {
            if (item == null)
                return;

            if (itemCollection.canUseFromCollection == false)
                return;

            int used = item.Use();
            if (used >= 0)
            {
                Repaint();
            }
        }

这里最终物品的使用时调用的Item Model中的Use()来实现的,这个应该是一种基类方法,需要特定的子类Item来实现,具体实现还要和容器有一定关系,比较复杂本文不表,有机会日后再展开,反正使用后调用了Repaint()方法也就是在使用完毕物品后装备格子进行了重绘刷新操作。

总结

把这四个问题回答完毕,基本本篇的主题也就清楚了,走人


原文地址:https://www.cnblogs.com/IlidanStormRage/p/5828265.html