这篇为自定义视图UI篇,我们将一起探索如何定制UI,以及UI相关的功能。
上一篇我们掌握了库存系统的基本功能。但如果开发一款RPG游戏那是远远不够的,需要大量的复杂的自定义UI,如多商店、快捷栏等等。相信看到这篇的同学早就是凤毛麟角了,那么不多说我们开始。
打开UI Designer上方一系统功能中的Inventory Grid,将即将创建的背包名称改为BagPanel,其余参数进行设置,如下图所示指定角色和UI管理器,点击Create。(在该操作前最好移至本章修复二、常量设置——进行图标修复)
Panel Option的参数,详情-样式说明链接
Basic(基本):只是一个带有面板组件的简单直反变换 Simple(简单):面板有一个带有标题的标题
Floating(浮动):面板有一个带有标题的标题,标题有一个拖动处理程序组件,允许玩家用鼠标移动面板
Main Menu(主菜单):面板是专门为主菜单内制作的。父变换必须是主菜单面板内容变换 Gird Option的参数
Gird (格子):以单独格子显示单独的物品 List(列表):以列表的形式显示
创建一个UI面板,再创建MainPanel脚本挂上,这样我们点击按钮或者按键B就可以直接切换出背包;背包会显示主角身上的物品。
别忘了拖拽两个组件到MainPanel脚本。
using System.Collections;
using System.Collections.Generic;
using Opsive.UltimateInventorySystem.UI.Panels;
using UnityEngine;
using UnityEngine.UI;
public class MainPanel : MonoBehaviour
{
public Button BagBtn;
public DisplayPanel BagPanel;
// Start is called before the first frame update
void Awake()
{
BagBtn.onClick.AddListener(() => {
SwichPanel(BagPanel);
});
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.B))
{
SwichPanel(BagPanel);
}
}
///
/// 切换界面
///
///
private void SwichPanel(DisplayPanel displayPanel) {
if (!displayPanel.IsOpen)
{
displayPanel.SmartOpen();
}
else
{
displayPanel.SmartClose();
}
}
}
网格分类 如下图分为三类,选中的背包UI下有三个类别按钮,可自行配置图标
库存系统间互动动作(各种交互,包含丢弃、删除、移动(到仓库)等动作)是通过Item Action驱动,可以称为物品的事件、动作等都有几分道理,这里统一为物品事件。
1、进入Item Actions创建两个资源到项目内(过程不做展示)
2、设置ItemAction
DebugAction
DropAction
3、创建事件面板
3.5、事件提示面板
Select Panel To Tooltip,专注红框内的4个属性,修改User Item Action Index为1。
Use Itme On Click: 是否点击触发
User Item Aciont Index:动作索引-1为全部触发,>=0为弹出功能窗
Auto Set Item User:自动选择用户
Max Number Of Actions:最大的动作数量
5、自定义动作:
创建自己的Item Action非常简单,只需创建一个继承Item Action并重写CanInvokeInternal和InvokeActionInternal函数的新类。
using Opsive.UltimateInventorySystem.Core.DataStructures;
using Opsive.UltimateInventorySystem.ItemActions;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class MyItemAction : ItemAction
{
///
/// 能否调用该操作
///
/// The item info.
/// The item user (can be null).
/// True if it can be invoked.
protected override bool CanInvokeInternal(ItemInfo itemInfo, ItemUser itemUser)
{
Debug.Log("可以进行我的操作");
return true;
}
///
/// 调用
///
/// The item info.
/// The item user (can be null).
protected override void InvokeActionInternal(ItemInfo itemInfo, ItemUser itemUser)
{
Debug.Log("这就是我的操作");
}
}
添加该Action,点击MyItemAction就会执行两个debug
虽说默认提供的已经够用,但该插件提供了一些特殊的接口和抽象的项目操作,在创建自定义项目操作时特别有用:
IActionWithPanel:允许您设置调用项目操作的面板。示例:移动项目操作ItemActionWithQuantityPickerPanel:从预制中生成一个QuantityPicker。
Panel,并在调用“真实”项目操作之前使用async/await获取数量。示例:数量删除项目操作。ItemViewSlotsContainerItemAction:引用项目视图插槽容器的项目操作。示例:打开其他项目视图插槽容器项目操作。
ItemActionWithAsyncFuncActionPanel<T>:生成一个异步函数操作面板,该面板可用于显示选项列表。示例:指定热键项操作
ItemObjectAction:用于引用项目对象,当项目用户无法从项目信息中识别相关项目对象时,该对象可能很有用。
6、代码调用-执行动作
示例-按键掉落物品
新建脚本ManuallyCallAction,然后添加脚本到角色身上运行,按L就会生成设置好的物体。别忘了拖拽m_Inventory、PickUpItemPrefab。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Opsive.UltimateInventorySystem.Core;
using Opsive.UltimateInventorySystem.Core.DataStructures;
using Opsive.UltimateInventorySystem.Core.InventoryCollections;
using Opsive.UltimateInventorySystem.ItemActions;
public class ManuallyCallAction : MonoBehaviour
{
//角色库存
public Inventory m_Inventory;
//掉落预制体
public GameObject PickUpItemPrefab;
void Update()
{
if (Input.GetKeyDown(KeyCode.L))
{
ManuallyCallDropItemAction();
}
}
public void ManuallyCallDropItemAction()
{
//要在代码中直接调用项目操作,您需要三样东西
//1) 项目信息 The ItemInfo
//2) 项目动作 The ItemAction
//3) 项目用户 The ItemUser
//首先拿到你想要的物品。
var result = m_Inventory.GetItemInfo(InventorySystemManager.GetItemDefinition("聚灵散"));
//如果结果没有价值,我们的库存中就没有聚灵散
if (result.HasValue == false) { return; }
//得到苹果的ItemInfo
var appleItemInfo = result.Value;
//拿到两个聚灵散
appleItemInfo = new ItemInfo(Mathf.Max(1, appleItemInfo.Amount), appleItemInfo);
//让我们获取 ItemUser。这通常放置在与 Inventory 相同的游戏对象中,但并非总是如此
var itemUser = m_Inventory.GetComponent<ItemUser>();
//现在让我们创建 drop item 操作,您可以缓存此对象并在需要时重用它以获得更高的性能
var dropItemAction = new DropItemAction();
//您必须在使用前初始化项目操作。 这仅能一次。
dropItemAction.Initialize(false);
//设置掉落预制体
dropItemAction.PickUpItemPrefab = PickUpItemPrefab;
//可选:检查您是否被允许调用该操作(无论如何它都会在内部执行此操作)
var canInvoke = dropItemAction.CanInvoke(appleItemInfo, itemUser);
//调用动作
dropItemAction.InvokeAction(appleItemInfo, itemUser);
//对于某些操作,您甚至可以使用额外的数据
//例如 drop action 跟踪最后一个掉落的拾取对象
var droppedPickup = dropItemAction.PickUpGameObject;
}
}
不要被这些代码吓到了,一般游戏是用不到定制功能的,如果用到了那说明肯定有人懂编程,交给砖业的来就好。
找到Item Description点击Go Tp跳转到物品说明这一部分
创建一个默认的面板,
参数Give Description a panel代表是否创建面板(创建的UI是否带有Display Panel脚本),
参数Item Description Preset代表物品描述的面板大小。
将Panelcontent下面的子物体拖入库存格子进行面板绑定,Convert To Tooltip就是设置提示(浮动)面板,需要固定的就不去点这个就可以。
对了,我们顺便加个背景适配一下项目,改成下图(具体看项目需求),默认居中。(建议改动Item Description Panel预制体)
效果
创建如图前两个程序,想要拖拽前两个是必要的,因为拖拽交换位置其实是两个功能组成:
拖拽程序:负责拖拽物品
OnItemViewSlotBeginDragE :在项目视图插槽开始拖动时触发。
OnItemViewSlotEndDragE:当项目视图槽完成拖动时触发。
OnItemViewSlotDragE:更新正在拖动项目视图插槽的每个帧。
通过侦听这些事件,拖动处理程序可以使用源项目视图插槽信息设置项目视图插槽光标管理器,并在拖动发生时告诉管理器鼠标的位置。
删除程序:负责处理掉落和删除物品(drop可以李姐成我们放下物品触发的动作)
通过下面第二个图片上的Item View Slot Drop Action Set里面的Action动作进行自定义
掉落条件
Item View Drop Container Can Add:检查来源和/或目的地是否可以接受交换。
Item View Drop Container Can Give:检查容器是否可以将物品交给另一个容器 Item View Drop Container Can
Move:检查是否可以将项目从一个索引移动到同一容器内的另一个索引。 Item View Drop Container Has
Name:检查源和/或目标容器的名称,看它们是否与指定的名称匹配。
Item View Drop From Item Collection:检查源项目集合是否与指定的项目集合相匹配。
Item View Drop Item Amount Condition:将源项目数量与最小值或最大值进行比较。
Item View Drop Null Item:检查源和/或目标项目是否为 Null。
Item View Drop Same Container:检查 Item View Slots 是否来自同一个 Item View Slots Container。
Item View Drop Container Can Smart Exchange:智能交换条件考虑了很多场景。
Item View Shape Drop:检查使用 ItemShapeGrid 时适合的
删除条件
Item View Drop Action To Item:从 Drop Action 调用 Item Action。
Item View Drop Container Exchange:通过从一个容器中移除并添加到另一个容器来交换容器之间的项目。
Item View Drop Container Give:将物品从源头送到目的地。 选择是否删除和/或添加项目。
Item View Drop Container 智能交换:智能交换条件考虑了很多场景。
Item View Drop Inventory Exchange:与上述类似,但不是通过 Item View Slots Container 添加/删除项目,而是直接在 Inventory 上执行此操作。
Item View Drop Inventory Give:与上面类似,但不是通过 Item View Slots Container 添加/删除项目,而是直接在 Inventory 上执行此操作。 Item View Drop Move Index 在同一个 Item View Container中移动 Item Stack 的索引。
Item View Drop Spawn Item Object:生成一个带有掉落物品的物品对象。
Item View Shape Drop:使用 ItemShapeGrid 函数放下项目,该函数考虑了 Item Shape。
4、扩展
默认设置基本能满足我们的项目,当然也可以创建自己的条件,只需继承“Item View Drop Condition”和“Item View Drop Action”抽象类:
[Serializable] public class ItemViewDropSameContainerCondition :
ItemViewDropCondition{
public override bool CanDrop(ItemViewDropHandler itemViewDropHandler)
{
return itemViewDropHandler.SourceContainer == itemViewDropHandler.DestinationContainer;
} }
[Serializable] public class ItemViewDropMoveIndexAction :
ItemViewDropAction {
public override void Drop(ItemViewDropHandler itemViewDropHandler)
{
itemViewDropHandler.SourceContainer.MoveItem(itemViewDropHandler.StreamData.SourceIndex,
itemViewDropHandler.StreamData.DestinationIndex);
} }
举个实用的例子,拖拽物体的时候光标变换成另外的样子
项目视图槽移动光标将侦听容器上的以下事件:
OnItemViewSlotSelected:选择了项目视图槽。
OnItemViewSlotClicked:单击项目视图槽。
1、创建一个物品栏保存到Setting文件夹下,默认名称为ItemSlotSet
2、创建想要的栏位,但是先别创建UI(图中Option为Simple,固定面板,建议大家统一选择Floating)
3、点击Create创建面板。
4、在角色的物品集合中多加一个装备栏类型,
5、添加点击动作,再创建Create Action Panelhe 和 Tooltip(参考本章的ItemAction)。如果装备栏点击后弹出框功能想不一样就创建个新的,如果不需要该功能就不用创建,根据需求自定义。
6、加上提示面板,具体参考本章的浮动信息面板部分
7、创建出来后,在场景创建装备栏UI,再在MainPanel脚本添加并拖入按钮和面板,以及打开关闭装备栏的方法。
1、打开Hotbar,输入想要的物品栏数量,创建。
2、添加交互操作,如图创建两个程序,第一个程序的action选之前背包选择的那个Item View Slot Drop Action Set。
3、效果
看起来还不错。
1、创建约束限制
项目限制是一个可编写脚本的对象,通过右键创建Create -> Ultimate Inventory System -> Inventory -> Item Restriction Set.
2、 限制说明
Group Item Restriction :可编程对象,本篇不做说明。详情
Item Collection Category Restriction: 限制某些物品栏只允许将特定类别的物品添加到其中。(具体见“管理物品集合”)
Item Collection Stack Amount Restriction : 只允许库存中一定数量的物品,限制可以容纳的物品数量。设置两个,十个格子只能放两个格子的物品;反之,不设置就是哪怕格子显示不下也在库存中无限储存。
Item Collection Stack Size Restriction:设置仓库堆叠上限,比如仓库最多能有99个相同属性的物体。
3、实例1
狗策划:限制16个格子能用,材料物品上限为99:
(1)修改库存名称为“玩家库存”
(2)设置玩家的物品限制
(3)在限制设置里面添加Item Collection Stack Amount Restriction和Item Collection Stack Size Restriction,填入刚刚改好的“玩家库存”,设置相应属性。
4、实例2
sb老板:前面设置了玩家库存最多可以存99;但我们限制要求相同材料9个为一组。
首先把“玩家库存”的类型改为MultiStackItemCollection
然后设置默认堆叠大小为9
最终效果
注意:如果材料没有堆叠,可能是你的父类属性设置了唯一值(独特),将其分类勾选取消即可。(上面的gif图中消耗品勾选了Unique故没有堆叠效果)
3、管理物品集合(Item Collection Category Restriction)
修改玩家库存为ItemTransactionCollection,添加一个独立物品集合和堆叠集合(堆叠集合上限默认99,需要的自行修改)
添加两个Item Collection Category Restriction条件,筛选总库存发送到新建的两个库存,方便以后管理和代码调用。
仓库(storage)就是另一个背包;仓库通常必须到某个地方或者某种一次性消耗品打开的类型,利用好的话游戏性也会大大增加。
(1)创建仓库
(2)添加仓库按钮和打开界面的事件(略)
(3)设置悬浮UI、点击面板、过滤器、搜索导航等(略)
(4)创建拖拽程序添加Action,也可以公用之前的,前提是背包功能和仓库一样。这部完成就可以拖拽了。(部分略)
(4)创建空物体添加Inventory库存脚本,拖入仓库作为仓库的库存。
效果(我只加了拖拽程序,其他的没加):
创建商店前请移至本章修复三、不匹配项目进行修复。
1、创建商店
2、给商店库存添加售卖物品(略)
3、给商店添加交互库存,也就是主角的库存
4、效果
5、要求商店删除购买的物品,并添加玩家出售的物品。
创建功能
Select该功能,勾选功能开关,有仅出售、仅购买、购买限制(之前铁显示x2但是实际上可以无限购买);我选择都打勾,就图个真实(互相买卖,互相伤害)
1、创建制作界面
UI面板支持WASD键进行切换物品,这就和移动的WASD冲突了,得想个办法鼠标停留在哪个UI,哪个UI就能使用WASD切换物品同时禁用移动。
添加新面板和脚本用来判断是否聚焦UI,注意不要有其他UI组件。
将UI管理的GamePlay Panel替换为刚刚创建的。
在Inventory Canvas找到PreventSelectableDeselection脚本,将上面的bool类型设为true。测试一下,发现只对了一半,鼠标放在UI上的时候还是能移动角色;
我们找到GamePlay Panel上的DisplayPanelSelector移除掉,添加一个新的脚本UIPanelSelector挂在该UI上,具体是为了继承该类,重写里面的方法,UIPanelSelector脚本如下(自由发挥,假设鼠标在快捷栏不需要屏蔽操作的话就剔除掉该界面,或者Switch进行多种界面的定制):
using Opsive.UltimateInventorySystem.UI.Panels;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class UIPanelSelector : DisplayPanelSelector
{
public override void OnPointerClick(PointerEventData eventData)
{
base.OnPointerClick(eventData);
SetPlayer(false);
}
public override void OnPointerEnter(PointerEventData eventData)
{
base.OnPointerEnter(eventData);
SetPlayer(false);
}
public override void OnPointerExit(PointerEventData eventData)
{
base.OnPointerExit(eventData);
SetPlayer(true);
}
///
/// 设置Player无法移动
///
///
private void SetPlayer(bool b) {
GameObject.FindGameObjectWithTag("Player").GetComponent<ExampleMove>().enabled = b;
}
}
我们创建出来的物品图标、描述都不显示在UI上,而插件的demo中各种图标、简介一应俱全;为什么呢?因为插件将图标定义为"Icon",代码再通过"Icon"找到对应的Sprite,最后给image附上对应的图片。
进入目录Assets\Opsive\UltimateInventorySystem\Scripts,创建InventoryConst脚本,用来管理库存相关的常量(经常用到且不变的数据)。
可能有小伙伴会疑惑:
为什么使用常量脚本定义,为了项目的后期维护。查看demo示例中的DemoInventoryDatabaseNames脚本会发现插件官方好像也定义了常量脚本但是却没使用,可能是未完成功能;官方说该脚本//This File is Auto-Generated by the editor window
,但我没找到在哪生成的;顺带一提,也可以按官方给出的常量脚本修改使用,dan。
为什么在Assets\Opsive\UltimateInventorySystem\Scripts目录下创建常量脚本呢,因为插件使用了一个程序集Opsive.UltimateInventorySystem来管理自身,如果建立到外部那么该程序集就不会获取外部的编译,想了解更多搜索.asmdef。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
///
/// 库存统一常量
///
public static class InventoryConst
{
public const string Icon = "图标";
}
以下为其他常量替换,大同小异,故不详细赘述。
不同版本的代码有所差别,例如新版将旧版商店界面的出售、购买两个字段移动至基类ShopBase。如遇到问题请检查版本,版本一致请留言给我,版本过新的话可以去官方论坛留言。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
///
/// 库存统一常量
///
public static class InventoryConst
{
public const string Icon = "图标";
public const string CategoryIcon = "类型图标";
public const string Description = "描述";
public const string No Item selected = "未选择";
public const string The item does not have a description = "未知";
public const string BuyPrice = "购买价值";
public const string SellPrice = "售出价值";
public const string EquipmentPrefab = "装备预制体";
public const string UsableItemPrefab = "可用物品预制体";
}
1、修改预制Item Description Big中的CurrencyItemView中价值字段。
2、例如Shop Menu、Crafting Menu预制当中的预制体可能都来自非Setting文件夹下(1.2.16版本),我的做法是将这些预制和对应的.meta直接替换Setting下同名的预制,然后修改UIDesignerSchema。
3、最后检查UIDesignerSchema,也可能会有脚本为空,搜索Setting文件夹下同名组件拖入即可。
本篇重构完成,结构比之前清晰不少,囊括了常用的UI功能。无庸赘述,各种UI功能实现皆在此篇当中。