unity 背包系统
前言
背包系统这个地方坑点还是很多的,照着视频做也费了很多劲.这个地方以后肯定是经常要碰到的,所以学到了什么东西就记录下来吧.
物品信息管理
物品信息管理的一大要求就是利用txt文件储存物品的属性,这些属性在背包系统的管理中非常有用.物品属性填写的格式可以按照下面这个表格:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
id | 名称 | icon名称 | 类型(Drug) | 加血量 | 加魔量 | 出售价 | 购买价 |
代码如下:
public class ObjectInfoListManager : MonoBehaviour {
public static ObjectInfoListManager _instance;
public TextAsset objectInfoListText;
public Dictionary<int, ObjectInfo> objectInfoDictById = new Dictionary<int, ObjectInfo>();
public Dictionary<string, ObjectInfo> objectInfoDictByName = new Dictionary<string, ObjectInfo>();
private void Awake()
{
_instance = this;
ReadInfo();
//print(objectInfoDict.Keys.Count);
}
public ObjectInfo GetInfoById(int id)
{
ObjectInfo info = null;
objectInfoDictById.TryGetValue(id, out info);
return info;
}
public ObjectInfo GetInfoByIconName(string iconName)
{
ObjectInfo info = null;
objectInfoDictByName.TryGetValue(iconName, out info);
return info;
}
public void ReadInfo()
{
string text = objectInfoListText.text;
string[] strArray = text.Split('\n');
foreach(string str in strArray)
{
ObjectInfo info = new ObjectInfo();
ObjectType type = ObjectType.Drug;
string[] proArray = str.Split(',');
int id = int.Parse(proArray[0]);
string name = proArray[1];
string iconName = proArray[2];
string str_type = proArray[3];
switch (str_type)
{
case "Drug":
type = ObjectType.Drug;
break;
case "Equip":
type = ObjectType.Epuip;
break;
case "Mat":
type = ObjectType.Mat;
break;
}
if (type == ObjectType.Drug)
{
int hp = int.Parse(proArray[4]);
int mp = int.Parse(proArray[5]);
int price_sell = int.Parse(proArray[6]);
int price_buy = int.Parse(proArray[7]);
info.id = id; info.name = name; info.icon_name = iconName; info.type = type;
info.hp = hp; info.mp = mp; info.price_sell = price_sell; info.price_buy = price_buy;
}
objectInfoDictById.Add(info.id, info);
objectInfoDictByName.Add(info.icon_name, info);
}
}
}
public enum ObjectType
{
Drug,
Epuip,
Mat
}
public class ObjectInfo
{
public int id;
public string name;
public string icon_name;
public ObjectType type;
public int hp;
public int mp;
public int price_sell;
public int price_buy;
}
背包界面
格子的设置
使用GridLayoutGroup来管理格子,可以控制格子的大小以及间隔.
背包界面的弹出
背包界面UI的动画,现在一般用DOTween.一般比较常用的是 DoXX 之类的函数.目前我采用下面这段脚本控制Ui动画:
public void Show()
{
Tween tweener = this.transform.DOMove(targetPosition, duration);
tweener.SetUpdate(true);
tweener.SetEase(Ease.OutQuad);
}
public void Hide()
{
float beginTime = Time.time;
Tween tweener = this.transform.DOMove(targetPosition + originPosition, duration);
tweener.SetUpdate(true);
tweener.SetEase(Ease.OutQuad);
// 设置延时
if (Time.time - beginTime > duration)
this.gameObject.SetActive(false);
}
其中SetEase是设置动画的播放形式,常用的是近对数曲线,也就是Ease.OutQuad
.
管理背包系统里的物品
UI拖拽功能
根据网上教程仿写UI拖拽功能. 拖拽功能要继承三个接口: IBeginDragHandler
, IDragHandler
, IEndDragHandler
,也有另外继承 IPointerDownHandler
,IPointerUpHandler
的.
尤其要注意的是,UI的Position是RectTransform,所以直接用世界坐标会出现问题.常见的解决方法是使用 Camera.main.ScreenToWorldPoint(Input.mousePosition)
或者 RectTransformUtility.ScreenPointToLocalPointInRectangle(imgRect, mouseDown, Camera.current, out mouseUguiPos)
.但是在我写的代码中这些都出了点问题,后来发现最简单的方法就行了
transform.position = Input.mousePosition;
代码如下:
public void OnBeginDrag(PointerEventData eventData)
{
if (canvasTra == null)
canvasTra = GameObject.Find("Canvas").transform;
lastParent = transform.parent; // 获取当前的父物体
transform.SetParent(canvasTra); // 将canvas设为父物体
isRaycastLocationValid = false; // 当前物体随着鼠标移动,需要设为穿透才可以获取被物体覆盖的格子
}
public void OnDrag(PointerEventData eventData)
{
transform.position = Input.mousePosition;
}
public void OnEndDrag(PointerEventData eventData)
{
// 获取终点位置鼠标指向的可能物体
GameObject Go = eventData.pointerCurrentRaycast.gameObject;
// 如果指向物体存在
if (Go)
{
// 如果指向的是空格子
if (Go.tag.Equals(Tags.inventory_gird))
{
SetParentAndPosition(transform, Go.transform);
}
// 如果指向的是物品
else if(Go.tag.Equals(Tags.inventory_item))
{
// 鼠标终点下也是一个物体时,我们默认交换位置
SetParentAndPosition(transform, Go.transform.parent);
SetParentAndPosition(Go.transform, lastParent);
if (transform.position == Go.transform.position)
{
Debug.LogError("物体重叠");
}
}
// 指向的位置无效
else
{
SetParentAndPosition(transform, lastParent);
}
}
// 如果没有指向任何物体
else
{
SetParentAndPosition(transform, lastParent);
}
isRaycastLocationValid = true;
}
public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
{
return isRaycastLocationValid;
}
private void SetParentAndPosition(Transform child, Transform parent)
{
child.SetParent(parent);
child.position = parent.position;
}
实现拾取物品的功能
这个标题我觉得取得还是不好.说白了这就是要实现物品数量的显示,物品位置的不重复.
我将功能再次细分,分为下面三个:CreateNewItem\PlusItemNum\CheckItem.
CreateNewItem 主要负责根据预制体添加Item,以及设置创建物体的父节点,名字和位置等信息.
PlusItemNum 主要负责添加物品的数量.
CheckItem 主要负责实时检查格子上所存在物体的信息,最好放在Update方法中.
代码如下:
public void CheckItem()
{
item = this.GetComponentInChildren<InventoryItem>();
if (item != null) // 如果不为空
{
SetGridOnShow(true, item.Id, item.Num);
}
else // 如果是空的,就清空数据
{
SetGridOnShow(false, 0, 0);
}
}
public void PlusItemNum(int addNum = 1)
{
item = this.GetComponentInChildren<InventoryItem>();
item.Num += addNum;
this.Num += addNum;
numLabel.text = this.Num.ToString();
}
public void CreateNewItem(int id, int num = 1)
{
// 根据id寻找预制体添加item
objectInfo = ObjectInfoListManager._instance.GetInfoById(id);
GameObject inventoryItem = Resources.Load(objectInfo.icon_name) as GameObject;
GameObject obj = GameObject.Instantiate(inventoryItem);
item = obj.GetComponent<InventoryItem>();
// 设置obj的父节点,位置,名字
obj.transform.SetParent(transform);
obj.transform.localPosition = Vector3.zero;
obj.name = objectInfo.icon_name;
// 设置item的id和num,用于检查
item.Id = id;
item.Num = num;
}
public void SetGridOnShow(bool isShow, int id, int num = 1)
{
// 设置Grid和显示
this.Num = num;
this.Id = id;
numLabel.text = num.ToString();
numLabel.gameObject.SetActive(isShow);
}
显示物品的信息
这里需要创建一个信息栏,然后在药品创建的时候将信息栏与该药品关联并隐藏.这里用到了一个小技巧:因为预制体没法在界面直接关联,所以只好用脚本寻找关联;但我们又不能让信息栏一直显示,所以需要将它"隐藏";因此我用到的小技巧是将信息栏的 localScale 设为 zero,这样可以达到目的又不至于药品创建时关联不到.
信息栏的显示和隐藏用普通的 OnMouseXX 不行,所以还是要继承一些接口重写函数.需要继承的接口是IPointerEnterHandle
, IPointerExitHandle
.
代码如下
private void Update()
{
if (this.enabled)
{
inventoryDes = GameObject.Find("InventoryDes").GetComponent<InventoryDes>();
}
}
public void OnPointerEnter(PointerEventData eventData)
{
//Debug.Log("鼠标进入");
inventoryDes.Show(this.Id);
}
public void OnPointerExit(PointerEventData eventData)
{
inventoryDes.Hide();
//Debug.Log("鼠标离开");
}