一、项目需求:
1、可以生成物品
2、提示物品相关信息
3、能拖拽背包中的物品进行交换、丢弃操作
二、实现步骤:
1,背包中的物品包括各种种类,如武器、消耗品、防具等,它们具备一些相同的属性,如ID、名字、描述、购买价格、出售价格、图标等,同时需要一个字段用于标志这个物品到底是什么类型的(让子类进行修改,最好的类型是枚举,这里我使用的是字符串),于是很容易想到建立一个基类Item:
public class Item
{
public int ID { get; private set; }
public int Num { get; private set; }
public string Name { get; private set; }
public string Description { get; private set; }
public int BuyPrice { get; private set; }
public int SellPrice { get; private set; }
public string Icon { get; private set; }
public string ItemType { get; protected set; }
public Item(int id, int num, string name, string description,
int buyPrice, int sellPrice, string icon)
{
this.ID = id; this.Num = num; this.Name = name; this.Description = description;
this.BuyPrice = buyPrice; this.SellPrice = sellPrice; this.Icon = icon;
}
}
同时衍生出其他所需要的物品类型,武器类需要攻击力的字段、消耗品类需要回复Hp、Mp数值的字段、防具需要力量、防御和敏捷的字段:
武器类:
public class Weapon : Item
{
public int Damage {get;private set;}
public Weapon(int id, int num, string name, string description,
int buyPrice, int sellPrice, string icon, int damage)
: base(id, num, name, description, buyPrice, sellPrice, icon)
{
this.Damage = damage;
base.ItemType = "Weapon";
}
}
消耗品类:
public class Consumable : Item
{
public int BackHp { get; private set; }
public int BackMp { get; private set; }
public Consumable(int id, int num, string name, string description,
int buyPrice, int sellPrice, string icon, int backHp, int backMp)
: base(id, num, name, description, buyPrice, sellPrice, icon)
{
this.BackHp = backHp;
this.BackMp = backMp;
base.ItemType = "Consumable";
}
}
防具类:
public class Armor : Item
{
public int Power { get; private set; }
public int Defend { get; private set; }
public int Agility { get; private set; }
public Armor(int id, int num, string name, string description,
int buyPrice, int sellPrice, string icon, int power, int defend, int agility) :
base(id, num, name, description, buyPrice, sellPrice, icon)
{
this.Power = power;
this.Defend = defend;
this.Agility = agility;
base.ItemType = "Armor";
}
}
2、制作背包系统的UI:
创建格子UI:
在Canvas下创建一个GridPanel作为背包的大背景,在GridPanel下创建格子背景GridBg,背景下放格子Grid,将格子作为预制体,给GridPanel添加一个组件GridLayoutGroup,根据需要修改Padding下的CellSize和Spacing属性,给GridPanel添加一个脚本GridPanelUI,用这个脚本来管理所有的格子,因为当需要生成物品时,我们需要知道背包中的空格子,在这个脚本中获取一个空格子。
创建物品UI:创建一个空物体Item,创建子物体包括Image(物品图片)和Text(数量),给Item添加脚本ItemUI,用于动态加载格子的内容,因为生成物品是随机的,会产生不同的物品。
3、如何得到物品的数据,于是我们需要另一个类KnapsackManager,其中包含一个字典public Dictionary<int, Item> ItemList;用于保存这些数据,编写一个Load方法,通过1中的类实例化一个个对象,再将对象保存到这个字典中即可,除此之外,还需要一个保存物品的方法,即:生成一个物品,将物品放到一个空格子下,注意:考虑到后来交换物品的操作,我们需要建立起格子和物品的对应关系,所以还需要一个类ItemModel处理这些数据(包括添加、查找、删除)
public void StoreItem(int itemId)
{
if (!ItemList.ContainsKey(itemId))
{
return;
}
Transform emptyGrid = GridPanelUI.GetEmptyGrid();
if (emptyGrid == null)
{
Debug.LogWarning("背包已满!");
return;
}
Item temp = ItemList[itemId];
CreateNewItem(temp, emptyGrid);
}
private void CreateNewItem(Item item, Transform parent)
{
GameObject itemPrefab = Resources.Load<GameObject>("Prefabs/Item");
ItemUI itemUI = itemPrefab.GetComponent<ItemUI>();
Sprite s = Resources.Load<Sprite>("Image/" + item.Icon);
itemUI.UpdateItemImg(s);
itemUI.UpdateItemNum(item.Num);
GameObject.Instantiate(itemPrefab, parent);
ItemModel.StoreItem(parent.name, item);//注意不要忘了每创建一个数据就要建立起格子和物品的对应关系
}
而在ItemModel中(具体代码不加以赘述):
public class ItemModel
{
private static Dictionary<string, Item> m_GridItem = new Dictionary<string, Item>();
public static void StoreItem(string name, Item item)
{
}
public static Item GetItem(string name)
{
}
public static void DeleteItem(string name)
{
}
}
4、如何产生物品,这里我们采用鼠标右键单击的方式模拟:
创建一个类InputDetector:
void Update()
{
if (Input.GetMouseButtonDown(1))
{
int index = Random.Range(0, 10);
KnapsackManager.Instance.StoreItem(index);
}
}
5、实现当鼠标移动到物品上时,会弹出一个提示框,制作一个提示框(注意ContentSizeFitter的使用),为提示框添加脚本TooltipUI:主要包括更新文本内容、显示、隐藏、跟随鼠标的功能
为格子创建一个GridUI脚本用于鼠标事件监测(鼠标进入、鼠标移出):
实现IPointerEnterHandler, IPointerExitHandler接口,暴露出对应的事件:
public static Action<Transform> OnEnter;
public static Action OnExit;
让KnapsackManager来监听:
当鼠标进入格子的时候就让TooltipUI显示并更新内容,当移出格子的时候就让TooltipUI隐藏起来
至于提示框的跟随则在Update中进行检测即可,但要注意屏幕坐标和世界坐标的转换
6、拖拽功能的实现与提示框的实现大体一致,只是实现的接口是
IBeginDragHandler,IDragHandler,IEndDragHandler,另外值得注意的是这两个事件携带的参数:
public static Action<Transform> OnLeftBeginDrag;
public static Action<Transform, Transform> OnLeftEndDrag;
当开始拖拽时传入的是当前鼠标下面的格子,将格子中的数据传递给DragItemUI,同时删除视图中的游戏物体
Destroy(gridTransform.GetChild(0).gameObject);
让DragItem跟随鼠标移动
而当拖拽结束时传入的两个参数,一个是当前拖拽的格子,一个是鼠标下面的游戏物体
当结束拖拽时分为以下几种情况:
当鼠标下方的transform为空,意为拖到了背包外面,想要丢弃物品,那么在ItemModel中删除拖拽的物品
当鼠标下方的物体为格子,又分为两种情况,一,格子下面没有物品,那么在ItemModel中删除拖拽的物品,重新建立格子和物品的对应关系,二,格子下面有物品,交换两个格子的物品,听起来似乎有些复杂,但实际上只需要分别得到两个格子,重新建立关系即可,注意要删除视图中的进入格子的物品
其他情况,则是在背包中,但有没有拖拽到格子上,则将物品返回到原处
if (enterTransform == null)
{
ItemModel.DeleteItem(prevTransform.name);
Debug.LogWarning("物品已扔");
}
else if (enterTransform.tag == "Grid")
{
if (enterTransform.childCount == 0)
{
Item item = ItemModel.GetItem(prevTransform.name);
if (item == null)
return;
ItemModel.DeleteItem(prevTransform.name);
CreateNewItem(item, enterTransform);
}
else
{
Destroy(enterTransform.GetChild(0).gameObject);
Item prevGridItem = ItemModel.GetItem(prevTransform.name);
Item enterGridItem = ItemModel.GetItem(enterTransform.name);
CreateNewItem(prevGridItem, enterTransform);
CreateNewItem(enterGridItem, prevTransform);
}
}
else
{
Item item = ItemModel.GetItem(prevTransform.name);
CreateNewItem(item, prevTransform);
}
三、重难点及遇到的问题:
1、物品之间的继承关系
2、用两个字典保存数据,一个是id与item对应的字典(模板数据),一个是格子与物品对应的字典(实例数据)
3、结束拖拽时的逻辑
遇到的问题:
当结束拖拽时鼠标下方为格子,而格子下没有游戏物体时具体又分为两种情况:
1、此格子为之前格子所在位置
2、此格子不为之前格子所在位置
看下面这段代码,如果是情况2没有问题,但如果是情况1呢?
if (enterTransform.childCount == 0)
{
Item item = ItemModel.GetItem(prevTransform.name);
if (item == null)
return;
CreateNewItem(item, enterTransform);
ItemModel.DeleteItem(prevTransform.name);
}
四、实现效果: