目录
声明
本教程学习均来自U3D中文课堂麦扣老师
1.Inventory UI 制作背包的基本UI
背包有的Canvas一定都是Ovrride,覆盖整个窗口界面,因为它要在最前端显示
创建一个Canvas名为Inventory Canvas
在这个Canvas当中除了实现我们的背包,还要实现人物的装备栏显示装备,还有我们的Action快捷栏的技能栏,以及我们快捷使用的物品栏,都将它们添加到这个Canvas里面。
接下来创建一个Panel,就像创建一个空物体一样做一个父级的归类,这样的话我们比较方便来调整我们的UI,
选择图片,将透明度选择为255,点击Set Native Size 调整为原来大小
放置到右侧,调整大小为500*700,可以看到图片被拉伸了,接下来我们要做2个操作:
首先是选择Inventory Canvas,修改它的尺寸,锁定在1920*1080,作为它基准的分辨率, 然后匹配宽和高0.5,
然后重新将他定位回来,将他的基准点设置为中心点,这样它就回到我们的中间了
不过我们看到图片被拉伸之后不那么好看,接下来我们调整一下这个图片,找到这个图片打开Sprite Editor
中间的位置可以随意拉伸,尽量保留四边本来的样子,这样的话图片看起来就会比较好看,修改完成后点击Apply
重新调整大小比例,放到屏幕右侧,更改名字为Inventory Bag
在背包打开的一开始我们希望顶上有一个名字,然后右上角有一个关闭的窗口
创建一个image 作为顶上Titkle的Bar,
创建一个子物体Text,这样就做好背包的Title了
然后接下来创建一个Button,在右上角作为一个关闭的按钮,所以选到我们的Inventory Bag,创建一个Button,
接下来就是非常重要的要做里面的格子,那如何让这些格子可以合理的排列呢?我们要做一个新的Panel,这个Panel用来管理这里所有的格子,并且按照我们规定的方法来进行排列,
命名为Inventory Contaniner用来管理每一个格子,是一个容器,设置为居中,修改大小,alpha值设置为0
接下来在下面创建第一个背包的样子,创建一个image,这个Image作为背包当中的每一个格子的背景,
如果我们一个一个去摆放它的话,势必会变得很乱,而且不方便 屏幕尺寸的变化,我们用到一个UI规则化的方法,添加 Lay out组件,这里添加的是网格Grid Layout
调整间隔,中间开始,每行最多5个格子
我们希望背包有30个格子,接下来将其他的格子删除,将第一个格子设置好,然后保存为一个Prefab,因为以后还需要再用到它
在这个格子里我们要显示我们每一个物品的图片,并且在右下角可以显示这个物品的数量,因为我们会有堆叠的物品那么就会有多个数量,或者只有单一数量我们都要在这里显示
刚才的思路就是用Image来显示物品的图片,以及一个Text文本来显示物品的数量
所以在Slot Holder下面创建一个Image,命名为ItemSlot,
在下面创建一个子物体Text表示数量删掉图片内容,在背包格子里有物品的时候直接将物品图片设置到上面就可以显示对应物体了
我们希望我们的Slot Holder可以出现在我们所有的背包里,甚至是我们的NPC的背包里都可以显示,将Slot Holder保存为预制体,提前将背包填满,复制出来30个Slot Holder
在这个教程当中就是使用固定好的背包数量,然后进行拖拽、叠加、使用物品,这样就制作好Inventory UI了
2.Item OnWorld 创建世界地图上的物品
Player子集下面有2个空栏,这两个空栏的作用是记录武器和盾牌在哪里生成
我们可以做的就是在地上触碰了武器,在对应的位置上生成出来就可以实现装备武器了,
熟悉一下这个逻辑:先将本身的盾牌和剑先隐藏掉,在场景中先添加武器
为剑添加材质Polyart
人物需要碰撞拾取它,那么这把武器需要有一个碰撞体,然后人物碰撞到它的时候执行一个代码:将地图上的这把剑删掉,在人物的手臂上生成。
在开始设置之前先将这把剑保存为Prefab,
它是世界地图上的一个武器,所以需要产生碰撞,就需要Rigidbody组件,还需要BoxCollider组件
还有一个重点就是为它添加代码了,接下来就创建一个代码,以后世界坐标上的物体我们都为他添加这个代码。
将代码添加到剑上
双击打开,在这个脚本当中判断触发器是否触发了用到OnTriggerEnter方法:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ItemPickUp : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
if(other.CompareTag("Player"))
{
//TODO:将物品添加到背包,
}
}
}
将物品添加到人物身上直接装备或者添加到背包都需要获取这个物品本身的属性,我们创建这个物品本身的属性就是ScriptableObject,创建代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "New Item",menuName ="Inventory/Item Data")]
public class ItemData_SO : ScriptableObject
{
}
ItemPickUp :创建Data的变量
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ItemPickUp : MonoBehaviour
{
public ItemData_SO itemData;
private void OnTriggerEnter(Collider other)
{
if(other.CompareTag("Player"))
{
//TODO:将物品添加到背包,
}
}
}
创建Sword的信息:
在prefab当中将信息拖拽进去
完善信息:
ItemData_SO:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum ItemType { Useable, Weapon, Armor }//可用物品,武器,装备
[CreateAssetMenu(fileName = "New Item",menuName ="Inventory/Item Data")]
public class ItemData_SO : ScriptableObject
{
public ItemType itemType;//物品类型
public string itemName;//物品名称
public Sprite itemIcon;//物品图标
public int itemCount;//物品数量
[TextArea]
public string description;//物品详情
public bool stackable;//是否可堆叠
}
保存在我们场景中的Prefab,它本身是有Rigidbody和Collider的,生成到Player身上的话就会掉在地上了,所以需要有另外的一个Prefab来当作生成的物体
创建新的文件夹Equipments,将剑拖入预制体,删除所有组件
这个Prefab就用来当地图上碰撞以后我们使用装备的时候是在玩家身上生成的模型,而下面一个才是拿来在世界地图上面碰撞的,
我们也要在数据当中来获得要生成的那把武器,所以如果是武器的话应该有详细的信息
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum ItemType { Useable, Weapon, Armor }//可用物品,武器,装备
[CreateAssetMenu(fileName = "New Item",menuName ="Inventory/Item Data")]
public class ItemData_SO : ScriptableObject
{
public ItemType itemType;//物品类型
public string itemName;//物品名称
public Sprite itemIcon;//物品图标
public int itemCount;//物品数量
[TextArea]
public string description;//物品详情
public bool stackable;//是否可堆叠
[Header("Weapon")]
public GameObject weaponPrefab;
}
人物移动碰撞到地上的武器,然后地上的武器消失,人物装备武器,在Player的Weapon下生成物品信息的Prefab
所以在哪里做这些工作呢?在装备武器会涉及到人物属性像攻击力有关的更改,所以将这些信息我们会写到CharacterStats里,因为这里是主要来计算属性信息的
首先我们来实现点击移动到这个物体的位置将这个物体删除
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ItemPickUp : MonoBehaviour
{
public ItemData_SO itemData;
private void OnTriggerEnter(Collider other)
{
if(other.CompareTag("Player"))
{
//TODO:将物品添加到背包,
//装备武器
Destroy(gameObject);
}
}
}
怎么点击移动到这个武器的位置呢?需要MouseManager添加一个物品的分类,我们为它添加物品的标签的时候鼠标会切换到手指的指针
void SetCursorTexture() //设置指针的贴图
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if(Physics.Raycast(ray,out hitInfo))
{
//切换鼠标贴图
switch(hitInfo.collider.gameObject.tag)
{
case "Ground":
Cursor.SetCursor(target, new Vector2(16, 16),CursorMode.Auto); //偏移(16,16)
break;
case "Enemy":
Cursor.SetCursor(attack, new Vector2(16, 16), CursorMode.Auto); //偏移(16,16)
break;
case "Portal":
Cursor.SetCursor(doorway, new Vector2(16, 16), CursorMode.Auto); //偏移(16,16)
break;
case "Item":
Cursor.SetCursor(point, new Vector2(16, 16), CursorMode.Auto); //偏移(16,16)
break;
default:
Cursor.SetCursor(arrow, new Vector2(16, 16), CursorMode.Auto); //偏移(16,16)
break;
}
}
}
void MouseControl()//返回鼠标左键点击返回值
{
if(Input.GetMouseButtonDown(0)&&hitInfo.collider != null)
{
if(hitInfo.collider.gameObject.CompareTag("Ground"))
{
OnMouseClicked?.Invoke(hitInfo.point); //当前OnMouseClicked事件如果不为空,将点击到地面上的坐标传回给这个事件(执行所有加入到onMouseClicked的函数方法)
}
if (hitInfo.collider.gameObject.CompareTag("Enemy"))
{
OnEnemyClicked?.Invoke(hitInfo.collider.gameObject); //当前OnEnemyClicked事件如果不为空,将点击到敌人的gameObject传回给这个事件(执行所有加入到OnEnemyClicked的函数方法)
}
if (hitInfo.collider.gameObject.CompareTag("Attackable"))
{
OnEnemyClicked?.Invoke(hitInfo.collider.gameObject); //当前OnEnemyClicked事件如果不为空,将点击到敌人的gameObject传回给这个事件(执行所有加入到OnEnemyClicked的函数方法)
}
if (hitInfo.collider.gameObject.CompareTag("Portal"))
{
OnMouseClicked?.Invoke(hitInfo.point); //当前OnMouseClicked事件如果不为空,将点击到地面上的坐标传回给这个事件(执行所有加入到onMouseClicked的函数方法)
}
if (hitInfo.collider.gameObject.CompareTag("Item"))
{
OnMouseClicked?.Invoke(hitInfo.point); //当前OnMouseClicked事件如果不为空,将点击到地面上的坐标传回给这个事件(执行所有加入到onMouseClicked的函数方法)
}
}
}
}
这样就实现了点击移动到这个物体的位置将这个物体删除了
3.Equip Weapon 装备武器
下面将真实的世界地图上的物品生成的武器装备到我们的Player身上,
先对Sword进行修改: 在添加一个Collider设置为Trigger,Box Collider取消Trigger,这样以后击败敌人可以掉落武器对地面实现交互碰撞
还需将Sword_eq的transform与角色Weapon下的武器Transform保持一致使得武器正常显示。
接下来我们修改代码实现装备武器,很可能以后我们会在装备武器的时候把天它的这个属性也调整到这个武器的攻击,所以修改CharacterStats这个脚本:
为了获得生成这个武器并且将武器装备到人物身上,我们要先获得Weapon的transform,这样就可以在它的下面生成一个子集物体,
CharacterStats:
[Header("Weapon")]
public Transform weaponSlot;
现在写一个方法让它在这个位置下面生成武器:
开一个region,以后我们有一个卸下武器或者切换武器的方法都可以写在这里
写一个装备武器的方法,需要武器的属性参数ItemData_SO
public class CharacterStats : MonoBehaviour
{
[Header("Weapon")]
public Transform weaponSlot;
#region Equip Weapon
public void EquipWeapon(ItemData_SO weapon)//装备武器
{
}
#endregion
}
回到ItemPickUp,实现一下碰撞后装备武器
public class ItemPickUp : MonoBehaviour
{
public ItemData_SO itemData;
private void OnTriggerEnter(Collider other)
{
if(other.CompareTag("Player"))
{
//TODO:将物品添加到背包,
//装备武器
GameManager.Instance.playerStats.EquipWeapon(itemData);
Destroy(gameObject);
}
}
}
接下来补充装备武器代码:
public void EquipWeapon(ItemData_SO weapon)//装备武器
{
if(weapon.weaponPrefab != null)
{
Instantiate(weapon.weaponPrefab, weaponSlot);
}
//TODO:更新属性
}
要为Player更新属性的前题,也要创建武器的攻击属性:
然后要将它加载到物品的属性里,所以有了装备的模型之后还有装备的属性
添加代码:ItemData_SO
public enum ItemType { Useable, Weapon, Armor }//可用物品,武器,装备
[CreateAssetMenu(fileName = "New Item",menuName ="Inventory/Item Data")]
public class ItemData_SO : ScriptableObject
{
public ItemType itemType;//物品类型
public string itemName;//物品名称
public Sprite itemIcon;//物品图标
public int itemCount;//物品数量
[TextArea]
public string description;//物品详情
public bool stackable;//是否可堆叠
[Header("Weapon")]
public GameObject weaponPrefab;
public AttactData_SO weaponData;
}
拿到数据
装备武器之后也要将实际的攻击力有一个变化,更新攻击的属性,需要做到的就是拿到weapon的攻击属性,然后将这个属性替换掉武器本身的属性
找到AttackData代码:创建一个应用武器的攻击属性方法
public class AttactData_SO : ScriptableObject
{
public float attackRange;//攻击距离
public float skillRange;//远程攻击距离
public float coolDown;//冷却时间
public float minDamge;//最小攻击数值
public float maxDamge;//最大攻击数值
public float criticalMultiplier;//暴击加成
public float criticalChance;//暴击率
public void ApplyWeaponData(AttactData_SO weapon)//应用武器的攻击属性
{
attackRange = weapon.attackRange;
skillRange = weapon.skillRange;
coolDown = weapon.coolDown;
minDamge = weapon.minDamge;
maxDamge = weapon.maxDamge;
criticalMultiplier = weapon.criticalMultiplier;
criticalChance = weapon.criticalChance;
}
}
更新属性:
CharacterStats:
public void EquipWeapon(ItemData_SO weapon)//装备武器
{
if(weapon.weaponPrefab != null)
{
Instantiate(weapon.weaponPrefab, weaponSlot);
}
//TODO:更新属性
attactData.ApplyWeaponData(weapon.weaponData);
}
将Player的基础攻击属性更改一下
现在就能装备武器更改攻击属性了
4.Inventory Data 创建背包的数据库
我们开始来写背包的逻辑,要实现背包的列表来管控里面的物品,然后要实现碰撞到地面上的物品之后将这个物品添加到我们的背包数据当中,
在Inventory Canvas上面创建一个代码,来管理关于背包的UI以及背包的逻辑
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName ="New Inventory",menuName = "Inventory/Inventory Data")]
public class InventoryData_SO : ScriptableObject
{
}
创建Inventory Data文件夹
Inventory就是一个库存管理有关的,我们身上的包裹,持有的物品的包裹算是一个库存,另外我们身上的装备栏也可能作为一个库存,快捷栏也是一个库存,这样的话可以在不同的数据库之间进行转换、拖拽,同样如果你以后会做商店,或者做其他的NPC做交换的时候,它也要有自己的Inventory Data,
下面创建背包的Inventory Data
接下来写一下它的逻辑:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName ="New Inventory",menuName = "Inventory/Inventory Data")]
public class InventoryData_SO : ScriptableObject
{
public List<InventoryItem> items = new List<InventoryItem>();
public void AddItem(ItemData_SO newItemData,int amount)//添加物品到背包中
{
bool found = false;
if(newItemData.stackable)//该物品可堆叠
{
foreach(var item in items)
{
if(item.ItemData = newItemData)//装备栏里有该物品
{
item.amount += amount;//该物品数量增加
found = true;//物品被找到
break;
}
}
}
物品不可堆叠 或者 可堆叠但装备栏里无该物品
for (int i = 0; i < items.Count; i++)//找到最近的一个格子来存放该物品
{
if (items[i].ItemData == null && !found)
{
items[i].ItemData = newItemData;
items[i].amount = amount;
break;
}
}
}
}
[System.Serializable]
public class InventoryItem
{
public ItemData_SO ItemData;
public int amount;
}
创建30个格子的数据
用Inventory Canvas来管理所有的逻辑以及UI,所以创建一个代码挂载在它身上来获得它的数据,以后我们也会获得Action Bar 以及我们的 Stats Bar,所有都用Inventory来管理
创建代码InventoryManager挂载到Inventory Canvas上
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class InventoryManager : Singleton<InventoryManager>
{
//TODO:最后添加模板用于保存数据
[Header("Inventory Data")]
public InventoryData_SO InventoryData;
}
下面实现将物品添加到背包:
ItemPickUp:
public class ItemPickUp : MonoBehaviour
{
public ItemData_SO itemData;
private void OnTriggerEnter(Collider other)
{
if(other.CompareTag("Player"))
{
//将物品添加到背包,
InventoryManager.Instance.InventoryData.AddItem(itemData,itemData.itemCount);
//装备武器
//GameManager.Instance.playerStats.EquipWeapon(itemData);
Destroy(gameObject);
}
}
}
这样就可以将物品添加到背包数据库了
5.Inventory UI 让背包显示物品
现在我们要实现UI部分的显示了,在做这个UI部分显示的时候我们先来看看它的构成是怎样的,
Inventory Canvas的第一个子物体指的是整个的Inventory Bag,指的是整个背包的窗口,后面我们也会做人物信息面板,上面包括人物的装备,以及下面横条的Action Bar,它们三个是并列的子物体,我们叫做Inventory Bag、Equipment Bag、以及Action Bag,所以Inventory Bag是指这个背包的整体,它的下面包含Contaniner,这个Contaniner里面包含很多Slot,每一个单元格里面我们要实现的功能,也包括将我们的物品信息显示出来,所以每一个Slot里面有自己的Item显示信息,我们在拖拽的时候也会将格子跟我们的数据库连接起来进行排序和调整,所以至少我们要创建3个脚本,第一个就是作为Contaniner作为它的UI,比如Inventory Contaniner,Action Contaniner等,另外就是我们的Slot Holder以及下面的Item Slot,Slot Holder来控制单元格里面去生产我们物品的基本信息,让它显示出来,去执行Item Slot里面本身的UI,
首先创建脚本:
逻辑图:
ItemUI:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ItemUI : MonoBehaviour
{
public Image icon = null;
public Text amount;
}
修改SlotHolder:
这样就不用Awake当中进行赋值拿到自身的变量了
ItemUI:创建显示物品UI的方法
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ItemUI : MonoBehaviour
{
public Image icon = null;
public Text amount;
public void SteupItemUI(ItemData_SO item,int itemAmount)//显示UI
{
if(item != null)
{
icon.sprite = item.itemIcon;
amount.text = itemAmount.ToString();
icon.gameObject.SetActive(true);
}
else
{
icon.gameObject.SetActive(false);
}
}
}
来到上一级SlotHolder:
先要拿到自己身上的ItemUI,然后创建单元格的类型,因为我们这个格作为Prefab来讲,我们希望这个Action Bar还有Equipment上面都有显示都使用它,所以我们要告诉它,你现在所在的UI面板是哪一种面板,比如说在背包里就不用快捷键来使用里面当前的物品,但是你在Action当中就要这么做,还有你是装备的话,我要在对应的位置实现对应的方法,比如说更新装备的样子,更新动画,更新数据,所以要创建一下枚举类型
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum SlotType { BAG,WEAPON,ARMOR,ACTION}//单元格类型:背包、武器、盾牌
public class SlotHolder : MonoBehaviour
{
public SlotType slotType;
public ItemUI itemUI;
}
拿到ItemSlot
接下来将每一个物品都Update上去:
public enum SlotType { BAG, WEAPON, ARMOR, ACTION }//单元格类型:背包、武器、盾牌
public class SlotHolder : MonoBehaviour
{
public SlotType slotType;
public ItemUI itemUI;
public void UpdateItem()//更新物品
{
switch(slotType)
{
case SlotType.BAG:
break;
case SlotType.WEAPON:
break;
case SlotType.ARMOR:
break;
case SlotType.ACTION:
break;
}
}
}
先完成第一个case的数据,在第一个case里面我要告诉它当前你的物品是属于背包里面的数据,并且我获得背包里对应序号的那个物品然后将它setup起来,我们在逻辑代码上面也提到了拿到序号是很重要的要一一匹配,另外我们也要获得的就是我们当前是属于哪一个背包的数据,因为在Inventory Data当中只有Inventory这一个数据,后边还会创建更多的数据,就是我们说的Action Bar也会有单独的数据库,所以要告诉你的ItemUI里面对应的数据库是哪一个,对应的序号是哪一个,所以在ItemUI创建2个变量:
ItemUI:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ItemUI : MonoBehaviour
{
public Image icon = null;
public Text amount = null;
public InventoryData_SO Bag { get; set; }//数据库
public int Index { get; set; } = -1;//序号
public void SetupItemUI(ItemData_SO item,int itemAmount)//显示UI
{
if(item != null)
{
icon.sprite = item.itemIcon;
amount.text = itemAmount.ToString();
icon.gameObject.SetActive(true);
}
else
{
icon.gameObject.SetActive(false);
}
}
}
SlotHolderr:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum SlotType { BAG, WEAPON, ARMOR, ACTION }//单元格类型:背包、武器、盾牌
public class SlotHolder : MonoBehaviour
{
public SlotType slotType;
public ItemUI itemUI;
public void UpdateItem()//更新物品
{
switch(slotType)//确定背包类型
{
case SlotType.BAG:
itemUI.Bag = InventoryManager.Instance.InventoryData;//背包为角色的背包
break;
case SlotType.WEAPON:
break;
case SlotType.ARMOR:
break;
case SlotType.ACTION:
break;
}
var item = itemUI.Bag.items[itemUI.Index];//数据库当中对应序号对应物品
itemUI.SetupItemUI(item.ItemData, item.amount);//显示UI
}
}
在ContaninerUI里面按照格子数量的顺序的序号把数值传到我们的Index当中,这样就匹配背包数据的30个格子了
创建数组:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ContaninerUI : MonoBehaviour
{
public SlotHolder[] slotHolders;
}
拖拽赋值:
接下来将背包里的物品生成出来了:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ContaninerUI : MonoBehaviour
{
public SlotHolder[] slotHolders;
public void RefreshUI()//刷新UI
{
for(int i = 0;i<slotHolders.Length;i++)
{
slotHolders[i].itemUI.Index = i;//给ItemUI编号赋值
slotHolders[i].UpdateItem();//更新物品
}
}
}
这样就写好了更新背包里主物品的样子了
最后在InventoryManager里面把每一个Container都执行下来,所以创建Container变量
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class InventoryManager : Singleton<InventoryManager>
{
//TODO:最后添加模板用于保存数据
[Header("Inventory Data")]
public InventoryData_SO InventoryData;
[Header("Containers")]
public ContainerUI inventoryUI;
}
按住Ctrl+R,Ctrl+R进行重命名Container;
拖拽赋值:
接下来在这里面实现通过InventoryManager来控制让他刷新背包里的格子,我们要实现碰撞物品之后他在格子里面能显示出来
public class InventoryManager : Singleton<InventoryManager>
{
//TODO:最后添加模板用于保存数据
[Header("Inventory Data")]
public InventoryData_SO InventoryData;
[Header("Containers")]
public ContainerUI inventoryUI;
private void Start()
{
inventoryUI.RefreshUI();//刷新UI
}
}
捡起物品刷新UI
public class ItemPickUp : MonoBehaviour
{
public ItemData_SO itemData;
private void OnTriggerEnter(Collider other)
{
if(other.CompareTag("Player"))
{
//将物品添加到背包,
InventoryManager.Instance.InventoryData.AddItem(itemData,itemData.itemCount);
InventoryManager.Instance.inventoryUI.RefreshUI();//刷新UI
//装备武器
//GameManager.Instance.playerStats.EquipWeapon(itemData);
Destroy(gameObject);
}
}
}
捡起武器:
发现图片没显示,且文字不在右下角,原因是Slot Holder添加了空物体ItemSlot没有更改位置
添加图标
现在就实现了让背包显示物品了。