Unity2D案例学习——背包系统制作(ScriptableObject)

00 简介

该学习案例来自b站up主M_Studio的系列视频背包系统,此篇博客为案例学习笔记

项目概览

在该项目中,

01 项目基础内容的搭建

系列视频的讲解中不包括基础场景的搭建以及角色移动控制的制作内容,在up提供的项目中已经提供了搭建好的基本内容
Unity2D案例学习——背包系统制作(ScriptableObject)_第1张图片
这一部分的笔记中对已搭建的部分进行一定的分析记录

素材处理

场景以及角色的素材来自Unity商店的免费资源【Tiny RPG Forest】,其中还包括除了环境和人物素材之外的手机要素等
Unity2D案例学习——背包系统制作(ScriptableObject)_第2张图片
项目中已经将素材分割并创建了画板,可利用Palette或素材包中的独立场景素材资源对地图进行进一步的绘制

场景搭建

场景通过单独的环境素材和Palette进行绘制,并分别添加了适当的碰撞体,其中Palette绘制的部分分为Ground和UpStuff两部分,前者为地面,后者为上层场景
Unity2D案例学习——背包系统制作(ScriptableObject)_第3张图片
而独立的环境素材统一整合在空物体Environment之下,并根据需要设立了单独的碰撞体
Unity2D案例学习——背包系统制作(ScriptableObject)_第4张图片

角色控制与动画切换

脚本实现

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
    Rigidbody2D rb;
    Collider2D coll;
    Animator anim;

    public float speed;
    Vector2 movement;

    private void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
        coll = GetComponent<Collider2D>();
        anim = GetComponent<Animator>();
    }

    private void Update()
    {
        Movement();
        SwitchAnim();
    }

    void Movement()//移动
    {
        movement.x = Input.GetAxisRaw("Horizontal");
        movement.y = Input.GetAxisRaw("Vertical");
        rb.MovePosition(rb.position + movement * speed * Time.deltaTime);

    }

    void SwitchAnim()//切换动画
    {
        if (movement != Vector2.zero)//保证Horizontal归0时,保留movment的值来切换idle动画的blend tree
        {
            anim.SetFloat("horizontal", movement.x);
            anim.SetFloat("vertical", movement.y);
        }
        anim.SetFloat("speed", movement.magnitude);//magnitude 也可以用 sqrMagnitude 具体可以参考Api 默认返回值永远>=0
    }
}

角色动画的创建

动画片段导入

Unity2D案例学习——背包系统制作(ScriptableObject)_第5张图片

动画状态机的控制

Unity2D案例学习——背包系统制作(ScriptableObject)_第6张图片

移动方向动画控制——混合树

角色移动的动画项目中使用了二维混合树来进行切换,通过horizontal和vertical两个标签进行控制(由代码中的SwitchAnim方法根据玩家操控输入进行调整)
Unity2D案例学习——背包系统制作(ScriptableObject)_第7张图片

02 GUI图形界面设置

创建背包GUI

1.在场景中创建一个Panel作为人物背包面板的基础样板,设置居中位置并调整合适的大小(视频中选择 515 415 的大小)
2.选择免费资源【Simple Fantasy GUI】中提供的素材替换Panel的Source Image中的基础样式(注意调整原始的透明度)
3.为了背包面板的视觉效果,在Bag下添加一个Image作为面板的title,选取资源中合适的素材导入。再这里可以通过按钮Set Native Size还原素材为原本的尺寸,也可以自行进一步调整Image的位置和大小
4.为Title添加具体文字。在Image下添加Text,设置素材提供的字体并调整位置大小等属性完成标题文字的填充
5.为背包面板创建按钮。在Bag Panel下创建一个Button,替换为适当的素材(注意还原大小的处理),并调整到合适的位置
6.通过按钮事件控制面板的关闭。在关闭按钮的On Click属性中,将Bag Panel拖入并选择事件[GameObject->SetActive(bool)],面板即可在点击按钮时关闭
完成如上步骤后,得到的界面如下:
Unity2D案例学习——背包系统制作(ScriptableObject)_第8张图片
6.创建背包格Grid。在Bag Panel下创建一个新的Panel,将Image替换为素材中的Inventory_Slots,并调整到适当的位置和大小(可以借助锚点处的定位系统)
7.设置物品描述文本区域。在Bag Panel下创建一个新的Text,设置适当的文本框区域以及适宜的字体大小,用于显示选定物品的描述

8.通过Grid Layout Group(网格布局组)组件实现背包栏位的图片寄放功能。为背包栏Grid添加Grid Layout Group组件,该组件为UGUI封装的布局脚本,可以对UI组进行简单的布局管理。在Grid下创建多个Image并拖入Grid Layout Group的判定区域,调整相应的属性设置后,即可得到如下的效果:
Unity2D案例学习——背包系统制作(ScriptableObject)_第9张图片
9.添加Text用以指示物品数量。经过Grid Layout Group布局的这些Image只要替换为相应的装备图片即可正常表达物品的存储,但还需要添加文本来显示物品存放的数量。在Image下添加Text,调整到适宜的相对位置,并默认设置为00
Unity2D案例学习——背包系统制作(ScriptableObject)_第10张图片
10.在Image上添加Button组件使之成为一个按键,用于之后点击装备时,能够在下方的物品描述区域生成相应的描述
11.将完成设置的Image保存为预制体以供使用
12.添加使用物品按钮。完成效果如下:
Unity2D案例学习——背包系统制作(ScriptableObject)_第11张图片

背包的唤出

通过编写相应代码使用户可以通过按键控制背包的唤出

void OpenMyBag()
	{
		isOpen = myBag.activeSelf;
		if (Input.GetKeyDown(KeyCode.Tab))
		{
			isOpen = !isOpen;
			myBag.SetActive(isOpen);
		}
	}

其中,myBag是背包面板的游戏对象引用,isOpen是标识表报是否被打开的布尔变量

03 数据库存储方法ScriptableObject

相关概念介绍:

ScriptableObject

简介:

ScriptableObject是一个允许你存储大量独立于脚本实例的共享数据的类,它的数据存储在 asset 资源文件中,
类似unity材质或纹理资源,也就是说,在游戏运行的时候是可以更改的,且在退出游戏的时候会保留更改的内容。

作者:厨子与画家
链接:https://www.jianshu.com/p/b394bc11ef06
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

CreateAssetMenuAttribute

即标签[CreateCreateAssetMenu(filename = “”,menuname = “”)]
使用后,在Project面板下的Create菜单中新建新的自定义子菜单,用于新建自定义资源

创建简单的游戏数据库

ScriptableObject脚本"Item"

1.新建脚本"Item",修改继承父类对象为"ScriptableObject"
2.添加如下的标签使可以在Unity中方便地创建自定义的ScriptableObject文件:

[CreateAssetMenu(fileName = "New Item", menuName = "Inventory/New Item")]
//创建一个新的资源菜单,菜单项名为"Inventory",子菜单项名为"New Item",所创建的文件名默认为"New Item"

在Unity中的效果如下:
Unity2D案例学习——背包系统制作(ScriptableObject)_第12张图片

为"Item"添加默认属性

1.在Item脚本中,为其添加如下的属性:

public class Item : ScriptableObject
{
	public string itemName;			//物品名称
	public Sprite itemImage;		//物品所使用的填充图片
	public int itemHeld;			//物品持有数量
	[TextArea]						//文本域标签(使string在Inspector窗口的编辑区从一行变为一个文本框)
	public string itemInfo;			//物品信息(可能多行)
}

此时,在Unity当中创建Item文件后,就可以为这些属性赋值:
Unity2D案例学习——背包系统制作(ScriptableObject)_第13张图片
2.配置基本属性后,将完成的"Item"文件保存在Item文件夹中归类

ScriptableObject脚本"Inventory"

1.新建一个ScriptableObject类型的脚本"Inventory",并添加如下的代码使之成为一个"背包"自定义资源模板,其中存放多个"Item
"

[CreateAssetMenu(fileName = "New Inventory",menuName = "Inventory/New Inventory")]
public class Inventory : ScriptableObject
{
	List<Item> Items = new List<Item>();
}

在Unity中效果如下:
Unity2D案例学习——背包系统制作(ScriptableObject)_第14张图片

实现背包效果逻辑

素材导入

在场景中导入需要使用到的素材,并配置相应的碰撞体(触发器模式),使其能够与玩家进行交互
Unity2D案例学习——背包系统制作(ScriptableObject)_第15张图片

脚本

编写脚本"ItemsOnWorld",用于场景中收集品的逻辑实现
代码如下:

public class ItemsOnWorld : MonoBehaviour
{
	public Item thisItem;					//素材所属的Item(物品配置文件)
	public Inventory thisInventory;			//素材所属的Inventory(背包配置文件)
}

Unity中效果如下:
Unity2D案例学习——背包系统制作(ScriptableObject)_第16张图片
继续完善代码:

private void OnTriggerEnter2D(Collider2D collision)//物品被人物碰到时
	{
		if (collision.gameObject.CompareTag("Player"))//若碰到物品的碰撞体为玩家
		{
			AddNewItem();//将物品添加到指定物品栏
			Destroy(gameObject);//在场景中摧毁物品
		}
	}

	public void AddNewItem()//将物品添加到指定物品栏
	{
		if (!thisInventory.ItemList.Contains(thisItem))//若指定物品栏中尚未存放该物品
		{
			thisInventory.ItemList.Add(thisItem);//在物品栏中添加物品
		}
		else
		{
			thisItem.itemHeld++;//物品持有数量增加
		}
	}

运行中,拾取配置好的物品,可以看到"背包"文件中成功存储了指定对象
Unity2D案例学习——背包系统制作(ScriptableObject)_第17张图片

其他(待补充)

除了上述使用以外,ScriptableObject还可以实现其他数据的保存,诸如角色属性,boss信息等

04 显示在背包里(C# code) InventoryManager

在之前的制作中,我们已经基本实现了物品信息和背包信息的基本存储功能,也完成了人物捡拾物品到背包数据库数据变更的基本交互。接下来应当完成在背包面板中合理的显示捡拾物品的相应信息

背包栏预制体Slot的处理

为背包栏填充预制体Slot创建一个脚本用以获取正确的物品信息。其代码如下:

public class Slot : MonoBehaviour
{
	public Item slotItem;	//物品所对应的物品信息存储
	public Image slotImage;	//Slot对应的图片组件,引用以在之后对其进行修改
	public Text slotNum;	//Slot对应的标识物品数量的Text组件引用
}

Unity2D案例学习——背包系统制作(ScriptableObject)_第18张图片

脚本InventoryManager

该脚本用来控制背包面板,在完成物品拾取后,可以通过调用其中的方法在背包面板的背包栏位中放入物品对应的Slot

其脚本初步实现如下

基本内容

public class InventoryManager : MonoBehaviour
{
	static InventoryManager instance;//单例模式
	//以便于在其他脚本中调用InventoryManager

	public Inventory myBag;			//角色背包数据库引用
	public GameObject slotGrid;		//背包栏引用
	public Slot slotPrefab;			//?背包栏填充预制体脚本引用
	public Text itemInfomation;		//物品描述文本引用

	private void Awake()
	{
		if (instance != null)//?如果有InventoryManager在场上,新上场的InventoryManager会销毁(保持只有一个Manager)
			Destroy(this);
		instance = this;
	}
}

根据捡拾物品对应填充背包面板

接着为InventoryManager创建相应地方法来完成数据的传输。在这个过程中,我们希望将相应的Item中存储的信息全部取出,通过InventoryManager传输给Slot。

public static void CreateNewItem(Item item)
	{
		Slot newItem = Instantiate(instance.slotPrefab,instance.slotGrid.transform.position,Quaternion.identity);
		//创建一个新的Slot,用以存储从对应Item中获得的数据
		newItem.gameObject.transform.SetParent(instance.slotGrid.transform);
		//将创建好的物品附着在Grid(背包栏)上

		//完成Slot中变量的赋值(获取需要的数据)
		newItem.slotItem = item;
		newItem.slotImage.sprite = item.itemImage;
		newItem.slotNum.text = item.itemHeld.ToString();
	}

ItemsOnWorld脚本的AddNewItem函数下(背包中尚未存在该物品)调用:

InventoryManager.CreateNewItem(thisItem);
			//每次人物碰撞拾捡场景中相应物品后,通过该语句将对应物品的Item传输到函数中
			//在函数中创建相应的背包栏的填充预制体到背包中,并完成相应数据的赋值

数据的更新

如果物品在背包中已经存在,则在捡拾后应当更新填充图像上的数字

public static void RefreshItem()
	{
		//为了避免代码的复杂混乱,这里考虑将Grid中全部内容清空,并重新从数据库中获取更新后的信息
		for (int i = 0; i < instance.slotGrid.transform.childCount; i++)
		{
			if (instance.slotGrid.transform.childCount == 0)
				break;
			Destroy(instance.slotGrid.transform.GetChild(i).gameObject);
		}

		for (int i = 0; i < instance.myBag.ItemList.Count; i++)
		{
			CreateNewItem(instance.myBag.ItemList[i]);//将数据库中所有的物品信息全部重新添加
		}
	}

在有如方法之后,先前在ItemsOnWorld脚本的AddNewItem方法下的新增代码可以直接舍弃,在AddNewItem最后替换为

InventoryManager.RefreshItem();//直接通过数据更新的方式完成背包栏中图像的生成

传输物品文本描述

完成上面的逻辑后,还需要完成在背包栏中物品描述信息的正确显示。在先前的设置中,背包栏填充图像的预制体上添加了Button组件,使得我们可以将之当做按钮来点击。我们希望在点击相应物品后,在背包栏下的区域显示设置好的描述信息。因此我们在Slot脚本中设置一个函数SlotOnClick来实现这个功能。而具体内容的显示我们通过InventoryManager进行控制:

private void OnEnable()//?
	{
		RefreshItem();
		instance.itemInfomation.text = "";
	}

在InventoryManager被创建初,文本描述区域显示为空
而需要更改物品描述时,使用如下函数:

public static void UpdataItemInfo(string ItemDescription)
	{
		instance.itemInfomation.text = ItemDescription;
	}

因此在Slot脚本中,SlotOnClick的实现如下:

public void ItemOnClick()//点击背包栏填充物品图像
	{
		InventoryManager.UpdataItemInfo(slotItem.itemInfo);
	}

完成代码编写后,更改Slot预制体中Button的设置:
Unity2D案例学习——背包系统制作(ScriptableObject)_第19张图片
至此,脚本的编写完成

5 实现拖拽效果(DragHandler接口)

本次内容主要实现功能:在背包中通过拖拽移动或调换装备所在的装备栏

对UI进行调整

在之前的项目中,Grid中只会根据背包数据库中实际所有的装备来生成Slot填充。为了实现装备的移动和调换,需要提前生成整个背包中所有的Slot

Grid

将先前为Grid设置的整块的背包栏背景图片抹去(透明度0),将Slot自身的填充图片设置为空背包格,然后用多个Slot填充Grid来实现原先的效果(需要适当调整Grid Layout Group的属性)
Unity2D案例学习——背包系统制作(ScriptableObject)_第20张图片

Slot

Grid整体的视觉效果调整完毕,接下来我们对Slot进行一些调整,以使之能够方便的完成我们需要的功能。对于物品拖拽的实现,再这里采取的思路是,Slot本身不再承载装备的素材图片,仅呈现出背包栏背景图片样式,实际的图片以及数字由Slot下的相关子物体承载。这样在没有物品时,只需将子物体的active属性设置为false即可正常显示背包栏背景,而对于物品的移动和交换,只需要将两个Slot下的子物体交换即可
有了如上的思路,下面我们开始对Slot进行调整
1.将Slot中的Button组件移除,单独为Slot创建一个子物体Button(删去Image)(Transition改为None)(调整为Slot大小)
2.为Button添加子物体Button用以在需要时承载装备图片(调整到Slot大小)
3.将原先的数字文本number作为Button的子物体
Unity2D案例学习——背包系统制作(ScriptableObject)_第21张图片
完成了上述设置后,手动将场景中Slot下的Item的active属性置false,即可看到正常的空置的背包栏位
Unity2D案例学习——背包系统制作(ScriptableObject)_第22张图片
接下来,我们进行代码部分的修正。在此之前,由于背包面板中已经开辟了18个格位,因此我们需要在数据库中也开辟相应数量的Item使得之后的处理符合逻辑(注意Item赋值置空)
Unity2D案例学习——背包系统制作(ScriptableObject)_第23张图片

对代码进行修正

InventoryManager

首先对InventoryManager中的代码进行修改
1.弃用CreateNewItem方法
2.修改对Slot的引用,从脚本Slot类型变更为GameObject
3.将脚本从Canvas上改为挂载到Bag上(注意重新赋值)
Unity2D案例学习——背包系统制作(ScriptableObject)_第24张图片
4.创建一个List用以存放生成的Slot

public List<GameObject> slots = new List<GameObject>();//存放Slot

5.调整Slot的生成代码

public static void RefreshItem()
	{
		//为了避免代码的复杂混乱,这里考虑将Grid中全部内容清空,并重新从数据库中获取更新后的信息
		for (int i = 0; i < instance.slotGrid.transform.childCount; i++)
		{
			if (instance.slotGrid.transform.childCount == 0)
				break;
			Destroy(instance.slotGrid.transform.GetChild(i).gameObject);
		}

		for (int i = 0; i < instance.myBag.ItemList.Count; i++)
		{
			//CreateNewItem(instance.myBag.ItemList[i]);//将数据库中所有的物品信息全部重新添加(舍弃)
----------->instance.slots.Add(Instantiate(instance.emptySlot));//生成Slot并添加到列表中
----------->instance.slots[i].transform.SetParent(instance.slotGrid.transform);//将生成的Slot添加到Grid中
		}
	}

Slot

在Slot脚本中添加新的方法:

public void SetUpSlot(Item item)
	{
		if (item == null)//若从数据库中传来的Item为空,则仅显示背景Slot
		{
			itemInSlot.SetActive(false);
			return;
		}
		//若从数据库取得的Item非空,则对应完成数据传递
		slotImage.sprite = item.itemImage;
		slotNum.text = item.itemHeld.ToString();
	}

其中使用了新的变量:

public GameObject itemInSlot;//Slot下的Item子物体引用

代码在InventoryManager的RefreshItem的第二个循环中调用:

instance.slots[i].GetComponent<Slot>().SetUpSlot(instance.myBag.ItemList[i]);

文本的更新

之前在点击物品时显示对应信息的逻辑是通过为Slot指定对应的Item,从Item中获取信息赋值来显示的。但在修正后的逻辑中,为了使背包栏之间能够发生交换,信息数据依附于Slot下的Item。因此在这里要重新为文本的显示编写代码。我们通过上面在Slot中编写的函数SetupSlot实现这个赋值:
首先添加新的变量:

public string slotInfo;//Slot文本信息

然后借助SetupSlot中接收的Item完成赋值,在原先的点击函数中传递新变量即可
完成后,游戏中物品信息以及可以在点击后正常显示:
Unity2D案例学习——背包系统制作(ScriptableObject)_第25张图片
(不知道为什么Grid Layout Group中的内容紧密贴合)

拾捡物品逻辑修正

在之前的设置中,如果角色在场景中拾捡了物品,将会在背包数据库List中添加新的一项存储相应物品信息。但在现在的项目中,数据库已经有了18个背包栏位,再增添新的栏位将会超出面板的正常显示范围,而且拾捡物品应当先放置在已有的空栏中。因此需要修改相关的代码来实现这一逻辑
将ItemsOnWorld中AddNewItem函数下if块中的代码更改为:

for (int i = 0; i < thisInventory.ItemList.Count; i++)
			{
				if (thisInventory.ItemList[i]==null)
				{
					thisInventory.ItemList[i] = thisItem;
					break;
				}
			}

另外,注意在RefershItem函数中Destory语句下新增一条语句以清空Grid下的slots列表,避免刷新后产生的问题

instance.slots.Clear();

实现装备拖拽的代码

拖拽事件接口和方法的添加

新建脚本ItemOnDrag用于控制拖拽物品的交互逻辑,脚本挂载在预制体Slot下的子物体Item中
为了实现拖拽功能,我们需要使用鼠标拖拽事件相关的类,分别是IBeginDragHandlerIDragHandlerIEndDragHandler这三个接口,包含在UnityEngine.EventSystem下:
Unity2D案例学习——背包系统制作(ScriptableObject)_第26张图片
(删除接口方法中不需要的默认的代码)
如上所示完成了接口的继承和方法的实现。在其中添加代码,使得在拖拽过程中Item可以和鼠标位置保持一致。

视觉效果的完善

1.由于Unity的渲染机制,在Hierarchy窗口中越靠下的对象越会在场景上方显示,因此使用上述代码从第一个格子将Item拖拽出来移动后,会发现Item会被其他Slot所遮挡。为了解决这个问题,我们希望在拖拽过程中将Item和父级Slot解绑,并改变渲染层级使之能够显示在Slot
上方。
2.此外,我们通过鼠标射线的方式来检测拖拽Item后Item所处的新的父级,但是由于鼠标下有我们所拖拽的Item对象,它会遮挡射线,因此我们为Item添加一个Canvas Group组件,通过其属性Blocks Raycasts来单独控制Item是否接受射线检测。只需要在代码中对这一属性进行控制,在拖拽过程中将属性关闭,就可以使鼠标射线正确获得其下方的Slot
Unity2D案例学习——背包系统制作(ScriptableObject)_第27张图片
Unity2D案例学习——背包系统制作(ScriptableObject)_第28张图片
(在这里可以使用Debug.Log(eventData.pointerCurrentRaycast)语句来更直观的查看运行过程中鼠标射线所指的对象)
射线的检测结果中,如果是"Item Image",即拖拽到的背包中有物品存在,则考虑进行交换,如果不是则直接设置父级和位置(这个处理帆方式存在指定到其他对象的问题,而且没有进行数据的传递,这里暂时不作处理)。实现代码如下:
Unity2D案例学习——背包系统制作(ScriptableObject)_第29张图片
全部代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;//包含UGUI拖拽相关接口的事件库

public class ItemOnDrag : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler//响应拖拽功能的接口
{

	public Transform originalParent;//Item的原始父级

	public void OnBeginDrag(PointerEventData eventData)//开始拖拽
	{
		originalParent = transform.parent;
		transform.SetParent(transform.parent.parent);//将Item父级设置到Slot的上层
		transform.position = eventData.position;//保持Item位置与鼠标拖拽位置一致
		GetComponent<CanvasGroup>().blocksRaycasts = false;//开始拖拽后取消射线检测以检测下方对象
	}

	public void OnDrag(PointerEventData eventData)//拖拽中
	{
		transform.position = eventData.position;//保持Item位置与鼠标拖拽位置一致
	}

	public void OnEndDrag(PointerEventData eventData)//结束拖拽
	{
		if (eventData.pointerCurrentRaycast.gameObject.name == "Item Image")//如果目标背包栏中有物品
		{
			transform.SetParent(eventData.pointerCurrentRaycast.gameObject.transform.parent.parent);//设置父级到目标背包栏
			transform.position = eventData.pointerCurrentRaycast.gameObject.transform.parent.parent.position;//设置位置到目标位置
			eventData.pointerCurrentRaycast.gameObject.transform.parent.SetParent(originalParent);//目标背包栏物品交换到原始父级
			eventData.pointerCurrentRaycast.gameObject.transform.parent = originalParent.transform;//目标背包栏物品交换到适宜位置
			GetComponent<CanvasGroup>().blocksRaycasts = true;//重新启用射线检测
			return;
		}
		//如果直接检测到Slot
		transform.SetParent(eventData.pointerCurrentRaycast.gameObject.transform);
		transform.position = eventData.pointerCurrentRaycast.gameObject.transform.position;

		GetComponent<CanvasGroup>().blocksRaycasts = true;//重新启用射线检测
	}
}

背包面板的拖拽(补充功能)

我们还希望在调出背包面板时,能够拖拽面板来调整其位置。因此接下来在Bag下创建新的脚本**“MoveBag”**来实现控制移动背包的功能。下面是具体的代码实现:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class MoveBag : MonoBehaviour, IDragHandler
{

	RectTransform currentRect;//当前的组件坐标

	private void Awake()
	{
		currentRect = GetComponent<RectTransform>();
	}

	public void OnDrag(PointerEventData eventData)
	{
		currentRect.anchoredPosition += eventData.delta;
		//anchoredPosition为矩形中心点(Pivot)与与锚点中心点(Anchors)之间的相对坐标
		//? 在拖拽过程中,使anchoredPosition值变化鼠标拖拽的位移(Vector2),使得整个对象的位置随之相应变化
		//? 若使用eventData.position会使得面板拖拽移动尺度太大且偏移中心点
	}
}

补充

在之前的项目中将InventoryManager挂载在了Bag上,但这会带来Bug:Inventory Manager的脚本如果挂在Bag上,在未启用Bag的时候无法触碰物品添加。所以还是挂在Canavs上,或者在Player的脚本的Awake一开始调用InventoryManager的RefreshItem()的方法来初始化所有的背包物品。

6 数据转换

在之前所完成的项目中,如果角色在场景中拾捡了物品,则会将数据更新到数据库并调用RefreshItem方法刷新背包。若之前将背包中已有的物品拖拽到了其他位置,此时仍会被刷新重置到背包前列。这是因为拖拽移动的物品在背包数据库中的列表顺序没有随着拖拽发生变化。在这里我们对此进行修正。

修改代码

在之前的代码中,我们在InventoryManager中创建了一个列表来存放场景中的Slot。我们可以通过这个列表将场景中Slot的信息来修改背包数据库中的信息,进而实现物品位置的数据变更

首先,在Slot中新增一个变量来记录Slot的编号
在这里插入图片描述
随后在InventoryManager的RefreshItem方法中为之赋值
Unity2D案例学习——背包系统制作(ScriptableObject)_第30张图片
接着来到脚本ItemOnDrag中做进一步的处理:
为了实现在拖拽时改变物品在数据库列表中的位置,我们借助上面设立的物品栏编号和数据库列表中的物品数据对应起来,完成位置的交换。为此我们需要引用Inventory中的物品列表,以及确定所拖拽的物品当前所在物品栏的编号
在这里插入图片描述
在开始拖拽时,我们从Item依附的父级Slot身上获取当前的编号
Unity2D案例学习——背包系统制作(ScriptableObject)_第31张图片
接下来分两种情况进行交换:
如果物品栏中已经有物品,那么创建一个新的中间变量temp,来交换拖拽的Item和目标背包栏的Item在数据库列表中的位置
在这里插入图片描述
如果目标物品栏中没有物品,则直接置于目标索引处,并将原始位置置空
在这里插入图片描述
再启动游戏,交换物品以及拾捡刷新后可以看到数据库中完成了数据的交换,背包面板也不会因刷新而重置格位了:
Unity2D案例学习——背包系统制作(ScriptableObject)_第32张图片

7 代码优化&Bug解决

关于拖拽Item

在拖拽Item时,有时会出现Item闪动到背包面板右下角的情况。这是因为在拖拽过程中Item会短暂存在于Grid层级下,由于Grid本身配置了组件Grid Layout Group,因此这时的Item会配适于该组件的排布规则而被规划到下一行。

解决方案:为Item添加Layout Element组件
Unity2D案例学习——背包系统制作(ScriptableObject)_第33张图片
选择其中的Ignore Layout使得Item忽略拖拽过程中父级Grid中的布局组件,就不再出现闪动的现象了

BUG:关于原地TP

由于之前代码的原因,如果在背包中拖拽起物品并将之放回原位,物品在数据库列表中会消失,因此需要在ItemOnDrag中补充一些代码:
在这里插入图片描述

BUG:关于拖拽物品到其他UI上

在之前的代码中,拖拽检测时对有其他物品的情况之外并没有进行处置,默认都当成了Slot,但这会导致拖拽的Item可以附着在其他UI上,因此需要对这部分代码进行补正,对Slot的检测情况包在一个if语句中(注意射线检测到的slot的名称),其他情况则补充语句,使物品回到原位:
Unity2D案例学习——背包系统制作(ScriptableObject)_第34张图片

补充:关于拖拽物品到面板外

脚本中拖拽物品时使用的检测射线只会识别UI对象,因此在拖拽到面板外时,物品会错误停留在面板外,发生异常(没有相应的代码处理这种读取对象为null的情况)。可以在这里补充相关的代码,来实现物品丢弃之类的功能或者弹回背包等。
在这里采取弹回背包的处理方式为上面的拖拽代码再套一个if语句来响应拖拽到场景的情况:
Unity2D案例学习——背包系统制作(ScriptableObject)_第35张图片

补充更正:

在这里插入图片描述
在上述的编写中此处错将 != 写作了 == ,在此更正
2.
Unity2D案例学习——背包系统制作(ScriptableObject)_第36张图片
上面编写检测逻辑时忘了加上else导致拖拽一定会回到原位,在此更正
3.
上面的检测逻辑中检测到其他物品时应该使用Image而不是Item Image,在此更正
4.
在这里插入图片描述
之前的代码编写错误没有成功修改位置,在此更正

你可能感兴趣的:(Unity实战项目学习)