游戏背包系统,“Inventory Pro插件”,研究学习-----妈妈再也不用担心我不会做背包了(Unity3D)

举报
恬静的小魔龙 发表于 2021/09/26 14:36:20 2021/09/26
【摘要】 推荐阅读CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客QQ群:1040082875 一、前言发现就喜欢研究这些插件,为什么呢,因为方便快捷啊。基本不用研究源代码怎么实现的,只要会有就行了。但是,光这样也不行,还是要多去看看底层代码是怎么实现的,还有人家的框架是怎么搭的。要不说Unity3D入门容易,提升难呢,因为提升全是靠苦功夫,去研究底层代码。算了,不絮叨了 二...

推荐阅读

一、前言

发现就喜欢研究这些插件,为什么呢,因为方便快捷啊。基本不用研究源代码怎么实现的,只要会有就行了。但是,光这样也不行,还是要多去看看底层代码是怎么实现的,还有人家的框架是怎么搭的。
要不说Unity3D入门容易,提升难呢,因为提升全是靠苦功夫,去研究底层代码。算了,不絮叨了

二、参考文章

Unity3D 装备系统学习Inventory Pro 2.1.2 总结
Unity3D 装备系统学习Inventory Pro 2.1.2 基础篇

三、正文

先上一张效果图
这里写图片描述
下载链接:
https://github.com/764424567/Unity-plugin/tree/master/Menu/Unity3D-S-Inventory
无效了记得跟博主说一声哈

1、总体结构

这里写图片描述

物品相关

  • 非UI相关InventoryItem 物品体系类,具体如装备,消耗品,商店物品等

  • UI相关InventoryUIItemWrapper 物品体系

  • UIItem的UI包装的Item继承体系

  • ItemCollection这样的类,因为简单的增、删和改肯定是逃不了,复杂的如交换,容器间的交换等操作

UI窗口相关

  • UIWindow体系的窗口类,具体有角色,银行,技能,店铺等窗口

  • InventoryUIDialog系统下的对话框类,具体有确认框,买卖,通用提示

  • 特殊窗口(非继承体系窗口),如上下菜单,通知窗口等

管理相关类

  • 配置管理

  • InvertoryManager

  • ItemManger

  • 数据库操作

其它

应该是一些辅助类,有UI部分的,事件辅助,定义接口等等吧,这部分还没有深入去阅读应该也是挺复杂的

剩下部分

一些第三方插件,Unity3D特性及Edit扩展等等

具体类图

这里写图片描述
这里写图片描述

2、使用教程

示范项目

在你的Assets/Demos/Scenes 你会发现这些演示场景,这些演示场景会包含所有特性,一定要仔细的看一下哦
这里写图片描述

建立一个新的项目

  • 选择1(自动)
    打开设置向导,它可以发现在Tools/Inventory Pro/Setup wizard
    对于错误“没有管理者发现的对象”;单击确定按钮和一个管理对象命名为"_managers"将自动添加到你的场景里。
  • 选择2(手动)
    创建一个空的游戏对象将inventorymanager组成,你可以找到在 库存/经理/ inventorymanager你会得到几个管理器组件包括inventorymanager,inventorysettingsmanager和更多的,我们不需要现在。

数据库建立

itemmanager包含所有项目的项目数据库,你可以创建管理每个场景,并使用不同的数据库,每一个场景。

1. 去你的项目窗口中,找到一个地方,你想创建项目数据库。

2. 右键单击(或点击创建,或者去创建/ 库存亲 /项目数据库)来创建一个新的项目数据库。

3. 一旦创建的数据库将在你的项目中选定。

4. 将项目拖到itemmanager的试题数据库槽。

现在也让我们创建 语言数据库 ,做完全相同的操作,并将它分配给inventorymanager的 lang 。

配置设置

首先打开主编辑器可以发现在 工具/库存亲/主编。当你打开编辑会给你一个错误消息的第一时间(见下图)。

这是因为库存亲需要知道它应该保存新项目。单击“设置”按钮,选择一个文件夹 文件夹里面你的项目。

现在让我们打开整合经理再次看到我们必须配置。

第一个选项卡“UI”包含用于渲染UI的基本要素。为了做到这一点需要至少2个项目的 项目按钮预置 Item Button Prefab和 UIROOT。

这个项目按钮预置是的预置 /槽包含的项目。根是包含所有用户界面窗口的UI画布。

几个演示器可以用来快速上手。你可以在库存/演示/资产/用户界面/ ui_prefabs / ui_item_pfb找到默认的包装

并新建一个UGUI,把UI Canvas 赋值给 GUI ROOT。

对象编辑

  1. 项目编辑

主编辑器,包括项目编辑器可以打开通过 Tools / Inventory Pro / Main editor

1.1 创建项目Creating items

首先,单击“创建项目”按钮,一旦我们做了一个对象选择器将显示。

在对象选择器,我们可以选择的物品类型。每一项类型具有不同的行为或目的,例如当消耗品被使用时将减少1个的堆栈大小,而当武器装备被装备时出现人物在场景中得到呈现。

你可以使用键盘来选择类型来创建,使用向上和向下箭头,选择一个你想要的并回车确认。

让我们抓住一个consummableinventoryitem (可消耗品)现在,并配置。

接下来你会被提示步骤2。在这里你可以选择一个模型,以此为基础的新项目。例如,你已经有了一个预置,例如一把剑,它拥有的纹理图片、可以使用碰撞器和自定义组件,那么只需拖动物品到“Drag object here”字段位置上,或者选择使用“Select model”按钮。

假设你没有一个预先定义的模型,只是想创建一个新的对象选择“No model”,或“2D sprite with trigger“用于2D游戏。

一旦创建的项目将显示一个按键在列表的底部,点击并生成物品。

一旦项目(预制)是创造了你可以修改它通过改变纹理,模型,添加自定义组件,等。

1.2 类别Categories

使用类别编辑器你可以定义项目的具体类别,例如食物,药水,剑,斧,等你可以定义每一类的冷却时间,这样你可以使用消耗品,同一类别中的所有其他项目也将在冷却。就是说,你可以重写每个项目的冷却时间在项目编辑器”选项卡。

1.3 性能Properties

使编辑更强大的性能也增加,属性允许您创建自定义的“变量”。一旦创建,你可以指定的属性项目编辑器内的任何项目,它提供一个值。 属性也可以通过 自定义代码。

值的字符串格式: 格式允许您使用该属性的值在UI格式。 使用{ NR }符号来定义自己的格式。

基准值: 基值的初始值的属性。例如,你可能有5的强度默认和允许它从那里成长。

1.4 稀有性编辑 Rarity editor

最后但并非最不重要的你可以定义你的项目多种多样,各有它的颜色,在工具提示中显示的用户界面元素。

定义物品的稀有程度分级,在UI中显示的颜色区别,还有物品掉落时出现在场景中的预置显示(这里默认是一个袋子)

——

2.装备编辑 Equipment editor

设备系统非常灵活,可用于几乎任何装备/附件系统。人物属性是通过选择一个或多个项目类型的定义,可以使用编辑器选择哪些数据将被计算,最终显示。

除了统计也有装备的类型,这些可以被定义在“装备类型编辑器选项卡 Equip type editor tab ”。装备类型可以限制避免某些组合。例如,当装备单手剑,我确信双手的剑和斧子和匕首双手不兼容。当单手剑装备,装备一把单手匕首,匕首将被装备。

——

3 货币编辑Currency editor

货币 编辑器允许你定义,可以用在你的项目,制定蓝图,供应商的货币,等。

每一种货币可以包含一组转换。这些转换允许你将一个美元兑欧元。

自动转换的定义是否可用于汽车。货币之间转换。例如,许多游戏使用系统的金,银和铜。当跑出来的铜系统可以转换到铜银货币。基本上重新填充它从一个更高的货币,可以转换成。

自动转换可以在编辑器底部的定义。

自动转换最大:允许你转换到一个更值钱的货币一旦你达到定义的最大值。例如,你不能拥有超过100的铜,所以一旦系统发现你有100以上的铜将被转换为1银。

自动转换分数: 当“让分数”(在顶部)不启用的分数也需要转换,或丢弃。例如,当你有1.1银(这是不允许的)系统会把它降到1银10铜。

4 制作编辑

制作经理类,允许你创建“锻造”任何时候,无论是烹饪,锻造或皮。

让我们创建一个新的 category,并开始创造一些蓝图。

#### 制定蓝图 Crafting editor

1. 默认情况下,结果项目名称–项目成功后,工艺–将作为蓝图的名称,但你当然可以,像往常一样,配置。

2. 机会因素表明工艺成功的可能性有多大,0.5种工艺的机会有50%的机会,和1的机会的因素有100%的成功机会。

1. 加速因子是成对的制作时间,在许多游戏(哇)制作一项变快时,创建一个完整的批一次。例如,当制作10烂苹果,第一项需要5秒,第二,1.1(10%)更快,这归结为5 /(1.1 ^ N)。

3. 所需物品说明很多项目所需的工艺给定的项目,这是从布局上分,可以在底部的定义。

一旦你定义的蓝图,你可以创建一个标准或布局的基础工艺窗口允许用户做他的 事。

语言编辑器 Language editor

语言编辑器允许你定义一个特定的动作发生在显示时的库存支持信息。

目前只有1个语言数据库的支持,为多语言数据库支持将来会增加。

如果你喜欢一个特定的消息不出现只是让它空着。

设置编辑

搜索

请注意,您还可以使用搜索栏搜索名称的变量设置/你想改变。

3、Demo解析

GettingStarted.unity

这里写图片描述
首先说一下Demo1的功能,其实很简单主要是建立起来Inventory Pro的运行环境,首先项目的Demo是3d的所以创建项目时,选择是3D工程。运行环境中,使用标准插件库建立一个第三方视角跟随的角色,角色可以在Panel中自由的移动跑跳;然后才是Inventroy Pro的基础配置,主要是引入Setting,在Setting中进行一些基础的配置。具体的运行后的界面如下图所示
这里写图片描述

设置一个角色

第一步在Scene中添加一个Panel,然后把它设置大点,不然角色会掉下去
这里写图片描述
第二步,找到图中的角色prefab然后直接拖到场景中,reset一下即可
这里写图片描述
第三方视角相机跟随

第三方视角相机跟随,也是按照标准过程进行

1,删除原来的MainCarmar摄像机

2,从Asset中拖拽我们需要的Prefab到场景中来
这里写图片描述
第三步,设置相机的Target为我们的控制角色,这里拖拽即可
这里写图片描述

最后是装备系统配置创建

基础环境创建好了,下面我们需要创建下装备系统的自身的基础环境了,涉及到了Srcript,Manage文件夹中的四大基础类

装备系统配置类,

装备系统管理类

Item管理类(工厂可能不准确,欢迎指正)

装备数据Asset类

Demo1中其实要实现的就两步

第一步,创建空游戏对象,配置InventorySetting类

第二步,初始化游戏Item数据Asset

4、实例

Demo1:使用插件来实现一个装备窗口

功能点:

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

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

  • 3、窗口具有拖拽功能

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

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

具体效果图如下所示:
这里写图片描述

插件使用

1、具体在UGUI 中的Canvas中创建一个InventoryWindow

2、在InventoryWindow下创建空GameObject并命名Container,赋予Grid LayOut 插件

3、给InventoryWindow添加InventoryUI组件,插件将自动添加WindowUI也就是通用窗口辅助插件

4、添加拖拽功能组件DraggableWindow,这样窗口就有了拖拽功能了

至此简单的点击I键可以打开和关闭的装备窗口做好了

总结

最后总结下实现通用窗口的三个类,分别是WindowHelper文件夹下的,UIWindow,UIWindowPage和DraggableWindow
这里写图片描述
1、DraggableWindow有就是拖拽窗口的组件,这里还是比较赞的,也是插件编程的简单例子,这里学过UGui的同学都知道要实现拖拽功能实现IBeginDragHandler和IDargHandler接口即可,原理很简单, 源码如下

using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;
 
namespace Devdog.InventorySystem
{
    [AddComponentMenu("InventorySystem/UI Helpers/DraggableWindow")]
    public partial class DraggableWindow : MonoBehaviour, IBeginDragHandler, IDragHandler
    {
        public float dragSpeed = 1.0f;
 
        private Vector2 dragOffset;
 
 
        public void OnBeginDrag(PointerEventData eventData)
        {
            if (InventorySettingsManager.instance.isUIWorldSpace)
                dragOffset = transform.position - eventData.worldPosition;           
            else
                dragOffset = new Vector2(transform.position.x, transform.position.y) - eventData.position;
        }
 
        void IDragHandler.OnDrag(PointerEventData eventData)
        {
            transform.position = new Vector3(eventData.position.x + dragOffset.x * dragSpeed, eventData.position.y + dragOffset.y * dragSpeed, 0.0f);
        }
    }
}

2、UIWindow这个类是窗口的公共类,先上类图主要功能点在类图上标注了,这里就不废话了,主要就是控制的窗口的显示关闭,及组合动画效果比较难的是实现了类似组合窗口的功能(这部分有后有机会再仔细分析)
这里写图片描述
源码就不全上了,上点有亮点的部分如下:

public virtual void Hide()
{
    if (isVisible == false)
        return;
 
    isVisible = false;
 
    if (OnHide != null)
        OnHide();
 
    if (hideAudioClip != null)
        InventoryUIUtility.AudioPlayOneShot(hideAudioClip);
 
    if (hideAnimation != null)
    {
        animator.enabled = true;
        animator.Play(hideAnimation.name);
 
        if (hideCoroutine != null)
        {
            StopCoroutine(hideCoroutine);                   
        }
 
        hideCoroutine = _Hide(hideAnimation);
        StartCoroutine(hideCoroutine);
    }
    else
    {
        animator.enabled = false;
        SetChildrenActive(false);
    }
}
 
 
/// <summary>
/// Hides object after animation is completed.
/// </summary>
/// <param name="animation"></param>
/// <returns></returns>
protected virtual IEnumerator _Hide(AnimationClip animation)
{
    yield return new WaitForSeconds(animation.length + 0.1f);
 
    // Maybe it got visible in the time we played the animation?
    if (isVisible == false)
    {
        SetChildrenActive(false);
        animator.enabled = false;
    }
}

以上部分是通过协程实现的具有延时效果的动画关闭窗口的代码,有代表意义。

3、UIWindowPage类,该类是UIWindow的子类,在UIWindow有一个Page的集合用于组合显示UIWindowPage,这块Demo1中没有涉及到该功能这里就不仔细分析了,等后面的例子中出现了再研究,亮点代码如下:

/// <summary>
/// Container that olds the items, if any.
/// </summary>
public RectTransform itemContainer;
public UIWindow windowParent { get; set; }
 
public override void Awake()
{
    base.Awake();
 
    windowParent = transform.parent.GetComponentInParent<UIWindow>();
    if (windowParent == null)
        Debug.LogWarning("No UIWindow found in parents", gameObject);
 
    // Register our page with the window parent
    windowParent.AddPage(this);
}
 
public override void Show()
{
    if(isEnabled == false)
    {
        Debug.LogWarning("Trying to show a disabled UIWindowPage");
        return;
    }
 
    base.Show();
 
    windowParent.NotifyPageShown(this);
}

这里UIWindow和UIWindowPage 本身是继承关系,然又彼此引用,代码可读性有些差了,作者这里通过Page类中Awake和Show来做父类的初始化和调用,也是一种方法,我觉得还行(请高手拍砖)。

总体来说目前的UIWinow和UIWindowPage更像是容器Panel或者Group不像是窗口,等以后的Demo中有复杂的再学习吧。

4、如何通过键盘唤起窗口

这个比较简单用到了U3D的输入输出模块,关键代码如下:

/// <summary>
/// Keys to toggle this window
/// </summary>
public KeyCode[] keyCombination;
 
public virtual void Update()
{
    if (keyCombination.Length == 0)
        return;
 
    bool allDown = true;
    foreach (var key in keyCombination)
    {
        if (Input.GetKeyDown(key) == false)
        {
            allDown = false;
        }
    }
 
    if (allDown)
        Toggle();
 
}

Demo2:通用窗口的具体实现

功能点:

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

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

  • 3、窗口具有拖拽功能

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

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

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

具体的插件使用和功能已经在上篇中说明了这里就不多说了

1、本篇重点分析 6通用窗口的类体系结构,类组织和类图如下所示:
这里写图片描述
类的继承体系结构这里就说了,在第一篇有可以自行查阅
这里写图片描述
类的引用关系、核心字段和方法已经在类图中标记的很清楚,用简单的几句话说明下,装备窗口中的每个格子是由一个空格子具有背景的UIItem和InventoryItemBase Model组成的,而整个装备窗口是一个InventoryUI,该类继承了ItemCollectionBase类,也就是说它是具有一组UIItem的装备集合窗口,添加上UIWindow组件、DraggableWindow就具有了普通窗口的拖拽移动和显示关闭的功能了。

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

如何实现动态装备窗口主要有两个核心技术:一个是UI中的自适应排列,也就是Grid layout Group组件;一个是U3D的prefab实例化技术

动态初始化Cell数据核心代码如下

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;
        }

是不是很简单 initailCollectionSize是InventoryUI基类的一个共有field也就是说这个装备格子的数量,这个可以根据自己设计的装备窗口手动设置,然后根据这个循环调用CreateUIItem<T>泛型方法通过,GameObject.Instantiate动态实例化预设装备格子对象,并设置Parent和位置

Demo3:通用窗口的具体实现可拖拽功能

窗口间物品的拖拽

自己在学习的过程中,虽然读了源码过了几天来写教程,还是有点不清楚,不能亲车熟路(这也许就是读与写的区别吧),给自己提出了几个问题,在重新去翻代码之前先给自己提出几个问题:

  • 1、拖拽的事件发起者应该是那个类?

  • 2、拖拽的事件的Drag是如何确定下方的UI元素的?

  • 3、拖拽后的逻辑操作,应该由哪个类来承接?

  • 4、窗口与窗口之间的拖拽,既有Drag又有Drop,如何更加合理的解决这个问题?

  • 5、窗口间物品拖拽的以及同窗口物品拖拽的逻辑流程是什么?

A1 拖拽的事件发起者应该是那个类?:拖拽的发起者必须是UI元素这点是必须的,目前涉及的UI元素有两种一种是窗口容器,一种数据装备格元素,显然装备格元素更有优势,因为少了一层定位逻辑判断,Drag事件直接发起,还可以做Move的相关逻辑,这里Inventory Pro2也确实是怎么做的(这里初次接触UGUI的同学可能要去学习下相关的事件系统,这里就不多说了),这里InventoryUIItemWrapper就是装备格的基类,这里继承了UGUI的相关UI事件,IBeginDragHandler, IEndDragHandler, IDragHandler, IPointerUpHandler, IPointerDownHandler, IPointerEnterHandler, IPointerExitHandler,这么多接口也预示着代码并不简单
这里写图片描述
这些就是接口实现函数,把他们都弄明白了也就明白了如何实现拖拽
这里写图片描述
A5 窗口间物品拖拽的以及同窗口物品拖拽的逻辑流程是什么?:先回答这个问题比较合理,在过去的winform的拖拽中并没有这么多接口可以实现,但是我相信拖拽操作的本身逻辑应该是不变的,也就是几个步骤,

  • 1)在物品上点击鼠标左键(记录鼠标点击的元素)->

  • 2)在鼠标不up,且move事件中确认了拖拽开始(Drag事件) –>

  • 3) mouse Move事件中获得鼠标下的元素->

  • 4)mouse up 事件触发Drop,判断鼠标位置及鼠标下元素是否可以drop如果可以进行Drop逻辑至此,这个拖拽操作结束

技术原型就是这么简单。下面看看Uintiy3d ugui Inventory Pro是如何实现的,又读了一遍代码,深深的有体会到了一把,“原理很简单,现实很残酷”,这还是在ugui为我们做了一些封装的情况下,这里其实涉及的函数其实有5个

  • OnPointerEnter:确定了点击了那个UI元素,对应1)

  • OnBeginDrag:开始拖拽,对应2)

  • OnDrag:拖拽中,对应3)

  • OnEndDrag:结束拖拽,对应4)

  • OnPointExit:清空选中元素,恢复默认值

具体代码比较多这里不再展开说了,这里庆幸的是,Inventory Pro对拖拽的逻辑进行了封装,在InventoryUIItemWrapper中接口实现函数中,主要做的参数舒适化,主要关于UI的逻辑代码封装在了InventoryUIUtility类中,

以下是主要接口实现函数的代码

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

            if (item != null && eventData.button == PointerEventData.InputButton.Left && itemCollection.canDragInCollection)
            {
                // Create a copy
                var copy = GameObject.Instantiate<InventoryUIItemWrapper>(this);
                copy.index = index;
                copy.itemCollection = itemCollection;

                var copyComp = copy.GetComponent<RectTransform>();
                copyComp.SetParent(InventorySettingsManager.instance.guiRoot);
                copyComp.transform.localPosition = new Vector3(copyComp.transform.localPosition.x, copyComp.transform.localPosition.y, 0.0f);
                copyComp.sizeDelta = GetComponent<RectTransform>().sizeDelta;

                InventoryUIUtility.BeginDrag(copy, (uint)copy.index, itemCollection, eventData); // Make sure they're the same size, copy doesn't handle this.
            }
        }

        public virtual void OnDrag(PointerEventData eventData)
        {
            if (item != null && itemCollection != null && itemCollection.canDragInCollection) // Can only drag existing item
                InventoryUIUtility.Drag(this, index, itemCollection, eventData);
        }

        public virtual void OnEndDrag(PointerEventData eventData)
        {
            if (item != null && itemCollection != null && itemCollection.canDragInCollection)
            {
                var lookup = InventoryUIUtility.EndDrag(this, index, itemCollection, eventData);

                // Didn't end on a button or used wrong key.
                if (lookup == null)
                    return;

                if (lookup.endOnButton)
                {
                    // Place on a slot
                    lookup.startItemCollection.SwapOrMerge((uint)lookup.startIndex, lookup.endItemCollection, (uint)lookup.endIndex);
                }
                else if (lookup.startItemCollection.useReferences)
                {
                    lookup.startItemCollection.SetItem((uint)lookup.startIndex, null);
                    lookup.startItemCollection[lookup.startIndex].Repaint();
                }
                else if(InventoryUIUtility.clickedUIElement == false)
                {
                    TriggerDrop();
                }
            }
        }

以下是InventoryUIUtility类封装的静态函数代码

public static InventoryUIDragLookup BeginDrag(InventoryUIItemWrapper toDrag, uint startIndex, ItemCollectionBase collection, PointerEventData eventData)
        {
            if (draggingItem != null)
            {
                Debug.LogWarning("Item still attached to cursor, can only drag one item at a time", draggingItem.gameObject);
                return null; // Can only drag one item at a time
            }

            if (eventData.button != PointerEventData.InputButton.Left)
                return null;


            draggingItem = toDrag;
            //draggingButtonCollection = collection;

            // Canvas group allows object to ignore raycasts.
            CanvasGroup group = draggingItem.gameObject.GetComponent<CanvasGroup>();
            if(group == null)
                group = draggingItem.gameObject.AddComponent<CanvasGroup>();

            group.blocksRaycasts = false; // Allows rays to go through so we can hover over the empty slots.
            group.interactable = false;

            var lookup = new InventoryUIDragLookup();
            lookup.startIndex = (int)startIndex;
            lookup.startItemCollection = collection;

            return lookup;
        }

        public static void Drag(InventoryUIItemWrapper toDrag, uint startSlot, ItemCollectionBase handler, PointerEventData eventData)
        {
            if(eventData.button == PointerEventData.InputButton.Left)
                draggingItem.transform.position = new Vector3(eventData.position.x, eventData.position.y, 0.0f);
        }

        public static InventoryUIDragLookup EndDrag(InventoryUIItemWrapper toDrag, uint startSlot, ItemCollectionBase handler, PointerEventData eventData)
        {
            if(eventData.button == PointerEventData.InputButton.Left)
            {
                var lookup = new InventoryUIDragLookup();
                lookup.startIndex = (int)draggingItem.index;
                lookup.startItemCollection = draggingItem.itemCollection;

                if (hoveringItem != null)
                {
                    lookup.endIndex = (int)hoveringItem.index;
                    lookup.endItemCollection = hoveringItem.itemCollection;
                }

                Object.Destroy(draggingItem.gameObject); // No longer need it

                draggingItem = null;
                //draggingButtonCollection = null;

                return lookup;
            }

            return null;
        }

        /// <summary>
        /// When the cursor enters an item
        /// </summary>
        public static void EnterItem(InventoryUIItemWrapper item, uint slot, ItemCollectionBase handler, PointerEventData eventData)
        {
            hoveringItem = item;
            //hoveringItemCollection = handler;
        }

        /// <summary>
        /// When the cursor exits an item
        /// </summary>
        /// <param name="item"></param>
        /// <param name="slot">The slot is the IButtonHandler index not the inventory index.</param>
        /// <param name="handler"></param>
        /// <param name="eventData"></param>
        public static void ExitItem(InventoryUIItemWrapper item, uint slot, ItemCollectionBase handler, PointerEventData eventData)
        {
            hoveringItem = null;
            //hoveringItemCollection = null;
        }

A2 拖拽的事件的Drag是如何确定下方的UI元素的?

这里DragDrop中的元素由 OnPointerEnter 来触发获得(有点像Mouse Move事件),具体保存在InventoryUIUtility类的一个静态变量中

public static InventoryUIItemWrapper hoveringItem { get; private set; }

A3 拖拽后的逻辑操作,应该由哪个类来承接?

也就是Drop的操作由谁来完成,首先回忆下职责InventoryUIItemWrapper类负责事件的触发,InventoryUIUtility类负责UI相关的逻辑(选中,射线,坐标系统)

再看一遍OnDragEnd函数,具体的Drop逻辑是有Drop的后查找的lookup集合类(装备格集合基类ItemCollectionBase)来处理的,具体又有交换\合并两个逻辑,触发代码如下:

if (lookup.endOnButton)
{
     // Place on a slot
     lookup.startItemCollection.SwapOrMerge((uint)lookup.startIndex, lookup.endItemCollection, (uint)lookup.endIndex);
}

当然还有一种复杂的逻辑,就是扔掉物品的操作,这个是有具体的Item装备模型类(InventoryItemBase)来处理,核心代码在TriggerDrop方法中来调用,具体如下:

public override void TriggerDrop(bool useRaycast = true)
        {
            if (item == null || itemCollection.canDropFromCollection == false)
                return;

            if(item.isDroppable == false)
            {
                InventoryManager.instance.lang.itemCannotBeDropped.Show(item.name, item.description);
                return;
            }

            Vector3 dropPosition = InventorySettingsManager.instance.playerObject.transform.position;
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, InventorySettingsManager.instance.maxDropDistance,
                InventorySettingsManager.instance.layersWhenDropping))
            {
                dropPosition = hit.point;
            }
            else
            {
                return; // Couldn't drop item
            }

            var s = InventorySettingsManager.instance;
            if (useRaycast && s.showConfirmationDialogWhenDroppingItem && s.showConfirmationDialogMinRarity.ID <= item.rarity.ID)
            {
                // Not on a button, drop it
                var tempItem = item; // Capture list stuff
                var msg = InventoryManager.instance.lang.confirmationDialogDrop;
                s.confirmationDialog.ShowDialog(msg.title, msg.message, s.defaultDialogPositiveButtonText, s.defaultDialogNegativeButtonText, item,
                    (dialog) =>
                    {
                        ItemCollectionBase startCollection = tempItem.itemCollection;
                        uint startIndex = tempItem.index;

                        var d = tempItem.Drop(dropPosition);
                        if (d != null)
                        {
                            startCollection[startIndex].Repaint();
                        }
                    },
                    (dialog) =>
                    {
                        //Debug.Log("No clicked");
                    });
            }
            else
            {
                var d = item.Drop(dropPosition);
                if (d != null)
                {
                    Repaint();
                }
            }
        }

A4 窗口与窗口之间的拖拽,既有Drag又有Drop,如何更加合理的解决这个问题?

这个问题比较绕,其实也涉及到了问题2,其实无论怎么拖拽也就是两个东西,一个是被拖拽的物体from,一个是要放的地方to,这里其实都是窗口中的格子,只要有了这两个格子类也就确定了from和to的容器,比较特殊的一种情况也就是 from和to两个容器相等,也就是同窗口拖拽了,具体这些对象InventoryUIUtilty类中都做了封装,还是很赞的具体代码如下:

public class InventoryUIDragLookup
        {
            public int startIndex = -1;
            public ItemCollectionBase startItemCollection;

            public int endIndex = -1;
            public ItemCollectionBase endItemCollection;

            public bool endOnButton
            {
                get
                {
                    return endItemCollection != null;
                }
            }
        }


        #region Variables 

        private static InventoryUIItemWrapper draggingItem;
        public static InventoryUIItemWrapper hoveringItem { get; private set; }
        public static bool isDraggingItem
        {
            get
            {
                return draggingItem != null;
            }
        }

        public static bool clickedUIElement
        {
            get
            {
                return EventSystem.current.IsPointerOverGameObject();
            }
        }


        public static bool isFocusedOnInput
        {
            get
            {
                if (EventSystem.current.currentSelectedGameObject != null)
                    if (EventSystem.current.currentSelectedGameObject.GetComponent<UnityEngine.UI.InputField>() != null)
                        return true;

                return false;
            }
        }

        #endregion

复杂的物品拖拽逻辑总结完毕,再次向我们印证了,从helloworld到现实是多么的困难,实际的情况可能更复杂比如要加入动画效果,要做网络延时验证,数据同步等等吧

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。