通用窗口类 Inventory Pro 2.1.2 Demo1(下续篇 ),物品消耗扇形显示功能
本篇想总结的是Inventory Pro中通用窗口的具体实现,但还是要强调下该插件的重点还是装备系统而不是通用窗口系统,所以这里提到的通用窗口类其实是通用装备窗口类(其实该插件中也有非装备窗口比如NoticeUI等)。
本篇涉及的功能用加出标出,具体的功能如下:
1、实现了两个窗口,通过点击键盘I来,打开或者关闭窗口也就是Toggle功能
2、装备窗口中的物品栏空格数量动态生成可控,可以在属性窗口手动配置
3、窗口具有拖拽功能
4、窗口物品具有拖拽,及窗口间拖拽
5、可以在窗口使用物品的功能,物品有消耗扇形显示功能
6、通用窗口的类体系结构
这里开动之前给自己提几个问题:
1、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中进行设置,
找到该预设,找到真相
看的出来这个预设绑定了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()方法也就是在使用完毕物品后装备格子进行了重绘刷新操作。
总结
把这四个问题回答完毕,基本本篇的主题也就清楚了,走人