siki背包系统(1) 详细笔记

这个笔记是我学习背包系统的时候做的笔记.

一共有5个部分,前4个部分都是笔记内容.后面一个是笔记的源码.基本上每一句复杂的代码都有注释,需要修改Unity里面内容的都有截图.再这个笔记中的第27条的时候有一个修改,也不是很大的修改.可以看一遍就跳过.

我做笔记的初衷是自己记录一下学习的内容.后面想搜索一些东西的时候发现网上笔记没有符合自己需要的,所以把这个笔记公开出来.大家互相探讨.有什么问题请私信我就好.


1.加载资源.
加载的资源(图片)要改变类型.要把图片类型编程2D and UI.
2.画出UML类图.

3.创建对应uml类图的C#类放在Unity中.
Item类 Consumable类 EquipmentType装备类 Weapon武器类 Materila材料类

(代码见代码笔记)
4.为了容易加载资源,要把图片资源放在Resources文件夹下面.

不需要加载的文加可以重新建立一个文件夹放进去.
5.处理Json文本.
通过 https://www.bejson.com/jsoneditoronline/ 来校验Json文本是否正确.
Json文件要改成UTF-8,不然有可能不支持中文.
Json格式:

json文件相当于一个数组,最开始用方括号,中间用大括号括起来表示不同的对象,不同的对象要用都好隔开.
6.创建InventoryManager物品管理器
在Hierarchy下面创建一个空物体,命名InventoryManager.再加下面添加脚本InventoryManager.
再InventoryManager里面要存储所有的物品.创建一个单例模式.
7.准备解析Json
在Project下创建一个文件夹Plugins.这个文件夹一般放置一些插件.这个文件夹里面的文件会被提前编译.
把上面这个文件放在Plugins里面.这时候打开VS就能看到C#中多了一个引用

这个时候就说明加载成功了.
把需要解析的文本放在Resources文件夹下,等下用来解析.
用于解析文件有一个Resources方法.所以一般用于解析或者是加载的文件都放在Resources文件夹下.
注:如果这个方式加载litJson不成功还有一种方式.
然后导入这个插件就好.然后把这个插件放在Plugins文件夹下面.
(导入这个插件的时候可能要登陆账号)



这时候打开VS就能看见绿色的JSONObject.这时候就说明导入成功了.

在VS中得到Json的内容.
itemList =  new  List<Item>(); //创建一个空的数组.
        TextAsset itemText = Resources.Load<TextAsset>("Items");//通过Resources加载文本.这里加载的时候注意,只要写文件名称就好,不用加后缀.json
         //文本类型的文档在Unity中会以TextAsset格式存储
         string  itemJson = itemText.text;
         //通过.text得到Json中的文本信息.
        JSONObject j =  new  JSONObject(itemJson); //上面得到了文本信息,现在再给他加上Json的格式
         //现在得到的j就是带有json格式的Items的文本.
8开始解析Json
string  typeStr = temp[ "type" ].str;
解析字符串用temp["xx"].str
int  id =( int ) temp[ "id" ].n;
解析int类型用temp["xx"].n
Item.ItemType type=(Item.ItemType)System.Enum.Parse(typeof(Item.ItemType), typeStr);
通过 System.Enum.Parse( typeof (Item.ItemQuality), temp[ "quality" ].str)来使一个Json对象转化成枚举类型.

         foreach  (JSONObject temp  in  j.list) //这里通过.list属性来获取j里面所有的对象,而不是直接访问j
        {
             //首先解析Type类型,因为物品是以这个分组存储的.
             //解析的方法是temp["xx"].str/n
             string  typeStr = temp[ "type" ].str; //通过j中的temp得到type的字符串
            Item.ItemType type=(Item.ItemType)System.Enum.Parse(typeof(Item.ItemType), typeStr);
             //通过System.Enum.Parse(type,value);来把一个对象转化成一个枚举类型
             //现在type就是一个枚举类型的数组了. 然后就可以用switch来处理了.
             //有很多公有的属性,可以放在外面,只把特殊的属性放在switch里面处理.
             //处理公有的属性
             int  id =( int ) temp[ "id" ].n;
             string  name = temp[ "name" ].str;
            Item.ItemQuality quality = (Item.ItemQuality) System.Enum.Parse(typeof(Item.ItemQuality), temp["quality"].str);
             string  description = temp[ "description" ].str;
             int  capacity = ( int ) temp[ "capacity" ].n;
             int  buyPrice = ( int ) temp[ "buyPrice" ].n;
             int  sellPrice = ( int ) temp[ "sellPrice" ].n;
             string  sprite = temp[ "sprite" ].str;
            Item item =  null ; //在这里创建一个空的Item.对应每一个不同的类分别赋值
             switch  (type) //特性类独有的属性在switch里面处理.
            {
                 case  Item.ItemType.Consumable:
                     //Consumable特殊属性就是hp和mp
                     int  hp = ( int ) temp[ "hp" ].n;
                     int  mp = ( int ) temp[ "mp" ].n;
                     //创建一个新的Consumable,注意这里是创建一个new Consumable,不是new Item
                    item= new  Consumable(id,name,type,quality,description,buyPrice,sellPrice,sprite,hp,mp);
                     break ;
                 case  Item.ItemType.Equipment:
                     // todo
                     break ;
                 case  Item.ItemType.Weapon:
                     // todo
                     break ;
                 case  Item.ItemType.Materila:
                     // todo
                     break ;
            }
             //在创建完成这个物体之后把这个物体加入到列表中.
            itemList.Add(item);
9.设计UI让UI和上面的数据联通.
在hirerarchya下面添加Panel改名为Knapsack作为背景,然后在Knapsack下再添加Panel,改名SlotPanel作为slot的背景,并且在 SlotPanel下面添加组件Grid Layout Group用来让它子物体排序 .在它下面添加Image作为slot.

10.把不需要和鼠标交互的UI的Raycast Target取消勾选.
在slot下增加一个button组件.把这个slot做成一个prefab.

11.在slot下面添加一个image,作为Item,在Item下面添加一个Text,作为Amount.
把Item做成一个prefab.注意:这里的solt和Item分别是2个prefab.

之后删除Item,把Slot复制多个,让背包完整.
12.Solt下面添加脚本Slot,用来管理每一个格子.Apply一下,应用到所有物体.这时候slot下面有2个脚本Button和Slot.

13.在Item下面添加ItemUI,这个脚本用来处理Item里面是什么UI,有多少数量.

14.在Knapsack下面创建脚本Knapsack.

15.做UML思维导图.

这里创建一个Inventory的脚本,让Knapsack和Chest继承Inventory.

16.编写Inventory脚本.
public  class  Inventory  : MonoBehaviour
{
     private  Slot[] slotList; //这里建立一个数组,用来管理所有slot.
   
        public  virtual  void  Start  () //这个Start方法子物体也有可能会调用,所以这里要用虚函数.
       {
           slotList = GetComponentsInChildren<Slot>();
         //因为Knapsack或者是Chest都继承自Inventory,所以他要GetComponentsInChildren.
         //其实也就是从Knapsack或者是Chest得到这个Slot组件
         //Knapsack/Chest都包含了这个Slot.这里得到Slot组件之后就能用SlotList[i]来控制这个组件了.
       }
        void  Update  () {}
}
17.在Inventory中保存Item.
保存Item有2种方法.一通过Item保存.二通过Id保存.
如果要通过Id保存Item,首先要得到Item.那么就要在InventoryManager中通过Id来得到Item.
InventoryManager中的代码如下:
     public  Item GetItemById( int  id) //通过Id返回一个Item.返回值是Item
    {
         foreach  (Item item  in  itemList) //遍历当前物品类型
        {
             if  (item.ID==id) //如果有任何一个物品的Id和现在的对比的id一样,返回该物品
            {
                 return  item;
            }
           }
         return  null ; //如果没有相同的id,返回空
    }
这样就通过Id得到了Item.
在Inventory中保存这个Item就好.

下面就是用代码形式解释上面的图.
这个代码在Inventory中.
     public  bool  StoreItem(Item item) //存储Item核心代码
    {
         if  (item== null ) //如果物品不存在直接报错并且返回false
        {
            Debug.Log("物品不存在");
             return  false ;
        }
         if  (item.Capacity==1) //容量上限为1
        {
            Slot slot = FindEmptySlot();//得到返回的solt
             if  (slot ==  null ) //如果得到的物品槽是空,输出没有得到物品
            {
                Debug.Log("没有空的物品槽");
                 return  false ;
            }
             else {slot.StoreItem(item);} //如果得到的物品槽不是空,调用Slot里面的存储方法.
        }
         else //容量上限大于1
        {
            Slot slot = FindSameIDSlot(item);
             if  (slot!= null )
            {
                slot.StoreItem(item);
            }
             else
            {
                Slot emptyslot = FindEmptySlot();//得到返回的solt
                 if  (emptyslot ==  null ) //如果得到的物品槽是空,输出没有得到物品
                {
                    Debug.Log("没有空的物品槽");
                     return  false ;
                }
                 else  { emptyslot.StoreItem(item); } //如果得到的物品槽不是空,调用Slot里面的存储方法.
                }
        }
         return  true ;
    }
上面的代码调用了几个方法,如下:
Inventory中:
     private  Slot FindEmptySlot() //创建一个寻找空格子的函数,并且把找到的空格子返回.
    {
         foreach  (Slot slot  in  slotList)
        {
             if  (slot.transform.childCount==0)
            {
                 return  slot;
            }
        }
         return  null ;
    }

     private  Slot FindSameIDSlot(Item item) //创建一个寻找相同Id的的物品槽,并返回物品槽
    {
         foreach  (Slot slot  in  slotList)
        {
             if  (slot.transform.childCount>=1&&slot.GetItemType()==item.Type&&slot.ISFilled()== false )
            {
                 return  slot;
            }
        }
         return  null ;
    }
Slot中调用的方法:
     public  void  StoreItem(Item item) //用于存储Item
    {
         // todo
    }

     public  Item.ItemType GetItemType() //得到当前物品槽存储的物品
    {
         //这里得到了相同的类型.我认为有问题.应该得到相同的Id才行
         return  transform.GetChild(0).GetComponent<ItemUI>().Item.Type;
         //返回这个物体的[0]子物体-下面的ItemUI组件的Item
         //这个Item已经在ItemUI里面被构造了. public Item Item { get; set; }
    }
     /*
    public Item GetItemId()
    {
        return transform.GetChild(0).GetComponent<ItemUI>().Item;
    }*/

     public  bool  ISFilled() //判断当前的物品槽是否满了
    {
        ItemUI itemUI = transform.GetChild(0).GetComponent<ItemUI>();
         return  itemUI.Amoint >= itemUI.Item.Capacity;
    }
在ItemUI中会要用到这个组件存储的Item和存储的数量,所以这里要构造:
     public  Item Item {  get ;  set ; } //这里用到了{get;set;}方法 所以这里的Item要大写
     public  int  Amoint {  get ;  set ; }
18.完善Slot中的存储功能.
首先在Solt中声明一个Gameobject,命名为itemprefab.将item拖进ItemPrefab中.

代码如下:
public  GameObject itemPrefab; //这里声明一个itemPrefab,用来实例化
    /*下面这方法用于存储item到slot中,这个方法只要完成存储功能就可以
    并不需要进行判断.因为调用这个方法的时候,已经完成了判断,调用这个方法的
    slot一定是可以存储的,所以下面的方法只要区分是让它数量+1,还是新实例化出
    来一个Item就可以了.*/
     public  void  StoreItem(Item item) //用于存储Item
    {
         if  (transform.childCount == 0) //如果是个空物品槽,实例化出来一个
        {
             //实例化出来一个itemPrefab.把这个itemPrefab作为Gameobject
            GameObject itemGameObject = Instantiate(itemPrefab)  as  GameObject;
             //把父类的位置和自身的位置重合
            itemGameObject.transform.SetParent(this.transform);
            itemGameObject.transform.localPosition = Vector3.zero;
             //这里他的父类和自身都是中心点设计,所以设置Vector3.zero(位置归零)
            itemGameObject.GetComponent<ItemUI>().SetItem(item);  //得到ItemUI组件,设置图片和数量.
        } 
         else //如果有相同类型的物品,调用ItemUI中的AddMount()方法
        {
            transform.GetChild(0).GetComponent<ItemUI>().AddAmount();
            //这里不能直接得到该物体的组件,而是它子物体的组件.
        }
    }
这里调用了ItemUI里面的SetItem方法:
     public  void  SetItem(Item item,  int  amount = 1)
    {
         this .Item = item;
         this .Amount = amount;
         // todo Update UI
    }
     public  void  AddAmount( int  amount = 1)
    {
         this .Amount += amount;
         // todo Update UI
    }
19.测试捡起物品的功能
在Unity中添加一个空物体,这个空物体下面挂Player脚本.
public  class  Player  : MonoBehaviour {
        void  Update  () {
              //按键G得到物品
            if  (Input.GetKeyDown(KeyCode.G))
           {
                int  id = Random.Range(1, 2); //随机得到一个物品包含最小值不好喊最大值
               Knapsack.Instance.StoreItem(id);//存储得到的物品
           }
       }
}
在Knapsack下面创建一个单例模式
     #region  单例模式
     private  static  Knapsack _instance;
     public  static  Knapsack Instance
    {
         get
        {
             if  (_instance= null )
            {
                _instance = GameObject.Find("KnapsackPanel").GetComponent<Knapsack>();
            }
             return  _instance;
        }
    }
     #endregion
20.更新UI
跟新UI只要跟新自身的图片,还有自身的数量显示更改,这里要更改这两个属性的话,需要在ItemUI中得到这2个组件.
     #region  UI组件
     //跟新UI包含图片和数量,要想跟新这图片和数量必须得到这2个组件
     private  Image itemImage;
     private  Text amountText;
     private  Image ItemImage
    {
         get
        {
             if  (itemImage ==  null )
            {
                itemImage = GetComponent<Image>();
            }
             return  itemImage;
        }
    }
     private  Text AmountText
    {
         get
        {
             if  (amountText ==  null )
            {
                amountText = GetComponentInChildren<Text>();
            }
             return  amountText;
        }
    }
     #endregion
     /*
    void Start()//这里不能用Start方法,因为这个item被实例化出来之后,马上就会执行下面的方法.strat方法来不及执行
    {
        //在start中得到这个图片和数量的组件
        itemImage = GetComponent<Image>();
        amountText = GetComponentInChildren<Text>();
    }
    */
     public  void  SetItem(Item item,  int  amount = 1)
    {
         this .Item = item;
         this .Amount = amount;
        ItemImage.sprite = Resources.Load<Sprite>(item.Sprite);
         //itemImage.sprite表示在itemImage下面的sprite组件.也就是这个组件的渲染图片
         //Resources.Load<a>(b);表示加载a类型的b路径的一个组件.
        AmountText.text = Amount.ToString();
         //表示让这个数量转化成字符串显示.
    }
     public  void  AddAmount( int  amount = 1)
    {
         this .Amount += amount;
        AmountText.text = Amount.ToString();
    }


21.ToolTip(提示框)的制作.
红色的四个角是锚点,用来定位面这个Text的缩放比例,如果他的锚点个外面的Image一致的时候,里面的Text的大小会随着外面的父类的大小等比缩放.
绿色的这四个边点是用来写文字内容的.这个范围就是写字的范围.


这里我们做ToolTip的时候,要让背景随着文字框的大小来改变,文字框的大小要随着文字的多少来改变.
那么,这个时候我们就要把:
背景作为文字框的子物体,在文字框上加上:
Content Size Fitter 脚本
调成Preferred Size

这样就可以用文字的多少来控制这个框的大小了.
但是此时文本会被背景遮挡,这时候在背景下在建立一个Text组件就好了.
主要,这里的2个文本要保持一致.

这里的Content只是没有Bg
和ToolTip完全一致.
如果我们想让一个提示框出现在我们鼠标的右下角,我们需要让它的中心点到他的左上面.
那么我们就要修改Pivot的值(0,1)

Povit的值的取值范围是0-1之间.
(0,0) 的时候在左下角
(1,1) 的时候在右上角
22.ToolTip脚本 设计ToolTip的显示和隐藏
在ToolTip下面添加ToolTip脚本

ToolTip不需要交互,所有全部的Raycast Target全部取消勾选.
添加Canvas Group脚本.通过控制Alpha值来控制显示和隐藏.

public  class  ToolTip  : MonoBehaviour
{
     private  Text toolTipText; //背景中显示的文本
     private  Text contenText; //显示的文本内容.Ps:因为背景中的内容会被遮罩,所以在背景的上面还有一个文本.
     //第一个文本主要是为了控制背景的大小,第二个文本时为了显示内容.
     private  CanvasGroup canvasGroup; //控制ToolTip显示和隐藏的组件.
     private  float  targetAlpha = 0; //目标值.0为隐藏.1为显示
     public  float  smoothing = 3; //隐藏速度.
        void  Start  ()
       {
           toolTipText = transform.GetComponent<Text>();
        contentText = GameObject.Find("Content").GetComponent<Text>();//这里报错,不能得到该组件,空指针.
         //找到问题了,不要用transform.Find()方法,改用全局变量搜索方法.
           canvasGroup = GetComponent<CanvasGroup>();          
       }
        void  Update  () {
            if  (canvasGroup.alpha!=targetAlpha ) //当当前值不等于目标值的时候,进行插值运算,渐变到目标值
           {
               canvasGroup.alpha = Mathf.Lerp(canvasGroup.alpha, targetAlpha, smoothing * Time.deltaTime);
             //因为当前值不能达到目标值,为了节省内存,让差值小于一个数的时候就看作相等.
             if  (Mathf.Abs(canvasGroup.alpha-targetAlpha)<0.01f)
               {
                   canvasGroup.alpha = targetAlpha;
               }
           }              
       }
        void  Show( string  text) //显示tooltip,text为显示的文本信息.
            {
                toolTipText.text = text;
                contenText.text = text;
                targetAlpha = 1;
            }

     void  Hide() //隐藏tooltip
    {
        targetAlpha = 0;
    }
}

23.用InventoryManager管理ToolTip的显示和隐藏.
在InventoryManager中增加下面代码.
     private  ToolTip toolTip; //得到ToolTip组件.
     void  Start ()
    {
        ParseItemJson();//开始解析Json
        toolTip = GameObject.FindObjectOfType<ToolTip>();
         //因为只有一个ToolTip类型,所以用FindObjectOfType来查找.
    }
     void  ShowToolTip( string  content)
    {
        toolTip.Show(content);
    }
     private  void  HideToolTip()
    {
        toolTip.Hide();
    }

24.在ToolTip上显示物品的信息.
当鼠标移动到slot上显示里面存储物品的信息.所以这个代码要放在slot上.
首先需要2个接口.IPointEnterHandle  IPointExitHandle
并且实现这2个接口.
public  class  Slot  : MonoBehaviour,IPointerEnterHandler,IPointerExitHandler
{
x
x
x
x
x
    //当鼠标移出物品槽的时候.
     public  void  OnPointerExit(PointerEventData eventData)
    {
         if  (transform.childCount>0)
        {
            InventoryManager.Instance.HideToolTip();
        }
    }
     //当鼠标放在物品槽上的时候.
     public  void  OnPointerEnter(PointerEventData eventData)
    {
         if  (transform.childCount>0)
        {
             string  content = transform.GetChild(0).GetComponent<ItemUI>().Item.GetToolTipText();
            //通过这个物品槽怎么得到显示内容:
             //得到这个物品槽的子物体,后再得到子物体的ItemUI组件,然后再得到这个组件的Item,最后调用这个GetToolTipText方法得到显示信息.
            InventoryManager.Instance.ShowToolTip(content);
        }
    }
}
这里ToolTip显示的内容 应该在Item上显示出来.
所以这应该在Item脚本上做功能.
     //返回物品的信息,这里用虚方法.这是因为这里是父类,子类不都一样,还会重写很多.
     public  virtual  string  GetToolTipText()
    {
         return  Name; // todo
    }
25.ToolTip的位置跟随.
ToolTip在InventoryManager中管理.
     private  bool  isToolTipShow =  false ; // //标志位,用来查看是否显示提示信息. 表示默认ToolTip不显示.
     private  Canvas canvas; //得到画布

     void  Start ()
    {
         ParseItemJson(); //开始解析Json
         toolTip = GameObject.FindObjectOfType<ToolTip>();
         //因为只有一个ToolTip类型,所以用FindObjectOfType来查找.
        canvas = GameObject.Find("Canvas").GetComponent<Canvas>();
    }
      void  Update ()
    {
         if  (isToolTipShow)
        {
            Vector2 position;
             //下面的函数用来得到鼠标的位置,并返回到position上.
            RectTransformUtility.ScreenPointToLocalPointInRectangle
                (canvas.transform  as  RectTransform, Input.mousePosition,  null ,  out  position);
            toolTip.SetLocalPosition(position+ new  Vector2(10,-10));
        }
    }
     public  void  ShowToolTip( string  content)
    {
        isToolTipShow =  true ; //标志位,用来查看是否显示提示信息.
        toolTip.Show(content);
    }
     public    void  HideToolTip()
    {
        isToolTipShow =  false ;//标志位,用来查看是否显示提示信息.
        toolTip.Hide();
    }
在给ToolTip位置的时候,要在ToolTip里面做这个功能.
     public  void  SetLocalPosition(Vector3 position)
    {
         this .transform.localPosition = position;
         //注意!!!这里是localPosition 而不是position.
    }
26.完善InventoryManager里面的Item.ItemType.Equipment
  switch  (type) //特性类独有的属性在switch里面处理.
            {
                 case  Item.ItemType.Consumable:
                     //Consumable特殊属性就是hp和mp
                     int  hp = ( int ) temp[ "hp" ].n;
                     int  mp = ( int ) temp[ "mp" ].n;
                     //创建一个新的Consumable,注意这里是创建一个new Consumable,不是new Item
                    item= new  Consumable(id,name,type,quality,description,capacity,buyPrice,sellPrice,sprite,hp,mp);
                     break ;
                 case  Item.ItemType.Equipment:
                     int  strength = ( int ) temp[ "strength" ].n;
                     int  intellect = ( int ) temp[ "intellect" ].n;
                     int  agility = ( int ) temp[ "agility" ].n;
                     int  stamina = ( int ) temp[ "stamina" ].n;
                    EquipMent.EquipmentType equipType =
                        (EquipMent.EquipmentType) System.Enum.Parse(typeof(EquipMent.EquipmentType),
                            temp["equipType"].str);
                    item= new  EquipMent(id, name, type, quality, description, capacity, buyPrice, sellPrice, sprite,strength,intellect,agility,stamina, equipType);
                     break ;
                 case  Item.ItemType.Weapon:
                     int  damage = ( int )temp[ "damage" ].n;
                    Weapon.WeaponType wpType =
                        (Weapon.WeaponType) System.Enum.Parse(typeof(Weapon.WeaponType), temp["weaponType"].str);
                    item =  new  Weapon(id, name, type, quality, description, capacity, buyPrice, sellPrice, sprite, damage, wpType);
                     break ;
                 case  Item.ItemType.Material:
                    item= new  Item(id,name,type,quality,description,capacity,buyPrice,sellPrice,sprite);
                     break ;
           }
27.完善描述信息.








在2018年2月26日 09:35:48出现冲突的修改:

1.加载资源.
加载的资源(图片)要改变类型.要把图片类型编程2D and UI.

2.画出UML类图.

3.创建对应uml类图的C#类放在Unity中.
Item类 Consumable类 EquipmentType装备类 Weapon武器类 Materila材料类

(代码见代码笔记)
4.为了容易加载资源,要把图片资源放在Resources文件夹下面.

不需要加载的文加可以重新建立一个文件夹放进去.
5.处理Json文本.
通过 https://www.bejson.com/jsoneditoronline/ 来校验Json文本是否正确.
Json文件要改成UTF-8,不然有可能不支持中文.
Json格式:

json文件相当于一个数组,最开始用方括号,中间用大括号括起来表示不同的对象,不同的对象要用都好隔开.
6.创建InventoryManager物品管理器
在Hierarchy下面创建一个空物体,命名InventoryManager.再加下面添加脚本InventoryManager.
再InventoryManager里面要存储所有的物品.创建一个单例模式.
7.准备解析Json
在Project下创建一个文件夹Plugins.这个文件夹一般放置一些插件.这个文件夹里面的文件会被提前编译.
把上面这个文件放在Plugins里面.这时候打开VS就能看到C#中多了一个引用

这个时候就说明加载成功了.
把需要解析的文本放在Resources文件夹下,等下用来解析.
用于解析文件有一个Resources方法.所以一般用于解析或者是加载的文件都放在Resources文件夹下.
注:如果这个方式加载litJson不成功还有一种方式.

然后导入这个插件就好.然后把这个插件放在Plugins文件夹下面.
(导入这个插件的时候可能要登陆账号)
(账号:我的qq邮箱
(密码:我的qq密码首字母大写

这时候打开VS就能看见绿色的JSONObject.这时候就说明导入成功了.
在VS中得到Json的内容.
itemList =  new  List<Item>(); //创建一个空的数组.
        TextAsset itemText = Resources.Load<TextAsset>("Items");//通过Resources加载文本.这里加载的时候注意,只要写文件名称就好,不用加后缀.json
         //文本类型的文档在Unity中会以TextAsset格式存储
         string  itemJson = itemText.text;
         //通过.text得到Json中的文本信息.
        JSONObject j =  new  JSONObject(itemJson); //上面得到了文本信息,现在再给他加上Json的格式
         //现在得到的j就是带有json格式的Items的文本.
8开始解析Json
string  typeStr = temp[ "type" ].str;
解析字符串用temp["xx"].str
int  id =( int ) temp[ "id" ].n;
解析int类型用temp["xx"].n
Item.ItemType type=(Item.ItemType)System.Enum.Parse(typeof(Item.ItemType), typeStr);
通过 System.Enum.Parse( typeof (Item.ItemQuality), temp[ "quality" ].str)来使一个Json对象转化成枚举类型.

         foreach  (JSONObject temp  in  j.list) //这里通过.list属性来获取j里面所有的对象,而不是直接访问j
        {
             //首先解析Type类型,因为物品是以这个分组存储的.
             //解析的方法是temp["xx"].str/n
             string  typeStr = temp[ "type" ].str; //通过j中的temp得到type的字符串
            Item.ItemType type=(Item.ItemType)System.Enum.Parse(typeof(Item.ItemType), typeStr);
             //通过System.Enum.Parse(type,value);来把一个对象转化成一个枚举类型
             //现在type就是一个枚举类型的数组了. 然后就可以用switch来处理了.
             //有很多公有的属性,可以放在外面,只把特殊的属性放在switch里面处理.
             //处理公有的属性
             int  id =( int ) temp[ "id" ].n;
             string  name = temp[ "name" ].str;
            Item.ItemQuality quality = (Item.ItemQuality) System.Enum.Parse(typeof(Item.ItemQuality), temp["quality"].str);
             string  description = temp[ "description" ].str;
             int  capacity = ( int ) temp[ "capacity" ].n;
             int  buyPrice = ( int ) temp[ "buyPrice" ].n;
             int  sellPrice = ( int ) temp[ "sellPrice" ].n;
             string  sprite = temp[ "sprite" ].str;
            Item item =  null ; //在这里创建一个空的Item.对应每一个不同的类分别赋值
             switch  (type) //特性类独有的属性在switch里面处理.
            {
                 case  Item.ItemType.Consumable:
                     //Consumable特殊属性就是hp和mp
                     int  hp = ( int ) temp[ "hp" ].n;
                     int  mp = ( int ) temp[ "mp" ].n;
                     //创建一个新的Consumable,注意这里是创建一个new Consumable,不是new Item
                    item= new  Consumable(id,name,type,quality,description,buyPrice,sellPrice,sprite,hp,mp);
                     break ;
                 case  Item.ItemType.Equipment:
                     // todo
                     break ;
                 case  Item.ItemType.Weapon:
                     // todo
                     break ;
                 case  Item.ItemType.Materila:
                     // todo
                     break ;
            }
             //在创建完成这个物体之后把这个物体加入到列表中.
            itemList.Add(item);
9.设计UI让UI和上面的数据联通.
在hirerarchya下面添加Panel改名为Knapsack作为背景,然后在Knapsack下再添加Panel,改名SlotPanel作为slot的背景,并且在 SlotPanel下面添加组件Grid Layout Group用来让它子物体排序 .在它下面添加Image作为slot.

10.把不需要和鼠标交互的UI的Raycast Target取消勾选.
在slot下增加一个button组件.把这个slot做成一个prefab.
11.在slot下面添加一个image,作为Item,在Item下面添加一个Text,作为Amount.
把Item做成一个prefab.注意:这里的solt和Item分别是2个prefab.
之后删除Item,把Slot复制多个,让背包完整.

12.Solt下面添加脚本Slot,用来管理每一个格子.Apply一下,应用到所有物体.这时候slot下面有2个脚本Button和Slot.

13.在Item下面添加ItemUI,这个脚本用来处理Item里面是什么UI,有多少数量.

14.在Knapsack下面创建脚本Knapsack.
 
15.做UML思维导图.
这里创建一个Inventory的脚本,让Knapsack和Chest继承Inventory.

16.编写Inventory脚本.
public  class  Inventory  : MonoBehaviour
{
     private  Slot[] slotList; //这里建立一个数组,用来管理所有slot.
   
        public  virtual  void  Start  () //这个Start方法子物体也有可能会调用,所以这里要用虚函数.
       {
           slotList = GetComponentsInChildren<Slot>();
         //因为Knapsack或者是Chest都继承自Inventory,所以他要GetComponentsInChildren.
         //其实也就是从Knapsack或者是Chest得到这个Slot组件
         //Knapsack/Chest都包含了这个Slot.这里得到Slot组件之后就能用SlotList[i]来控制这个组件了.
       }
        void  Update  () {}
}
17.在Inventory中保存Item.
保存Item有2种方法.一通过Item保存.二通过Id保存.
如果要通过Id保存Item,首先要得到Item.那么就要在InventoryManager中通过Id来得到Item.
代码如下:
     public  Item GetItemById( int  id) //通过Id返回一个Item.返回值是Item
    {
         foreach  (Item item  in  itemList) //遍历当前物品类型
        {
             if  (item.ID==id) //如果有任何一个物品的Id和现在的对比的id一样,返回该物品
            {
                 return  item;
            }
           }
         return  null ; //如果没有相同的id,返回空
    }
这样就通过Id得到了Item.
在Inventory中保存这个Item就好.

下面就是用代码形式解释上面的图.
这个代码在Inventory中.
     public  bool  StoreItem(Item item) //存储Item核心代码
    {
         if  (item== null ) //如果物品不存在直接报错并且返回false
        {
            Debug.Log("物品不存在");
             return  false ;
        }
         if  (item.Capacity==1) //容量上限为1
        {
            Slot slot = FindEmptySlot();//得到返回的solt
             if  (slot ==  null ) //如果得到的物品槽是空,输出没有得到物品
            {
                Debug.Log("没有空的物品槽");
                 return  false ;
            }
             else {slot.StoreItem(item);} //如果得到的物品槽不是空,调用Slot里面的存储方法.
        }
         else //容量上限大于1
        {
            Slot slot = FindSameIDSlot(item);
             if  (slot!= null )
            {
                slot.StoreItem(item);
            }
             else
            {
                Slot emptyslot = FindEmptySlot();//得到返回的solt
                 if  (emptyslot ==  null ) //如果得到的物品槽是空,输出没有得到物品
                {
                    Debug.Log("没有空的物品槽");
                     return  false ;
                }
                 else  { emptyslot.StoreItem(item); } //如果得到的物品槽不是空,调用Slot里面的存储方法.
                }
        }
         return  true ;
    }
上面的代码调用了几个方法,如下:
Inventory中:
     private  Slot FindEmptySlot() //创建一个寻找空格子的函数,并且把找到的空格子返回.
    {
         foreach  (Slot slot  in  slotList)
        {
             if  (slot.transform.childCount==0)
            {
                 return  slot;
            }
        }
         return  null ;
    }

     private  Slot FindSameIDSlot(Item item) //创建一个寻找相同Id的的物品槽,并返回物品槽
    {
         foreach  (Slot slot  in  slotList)ID
        {
             if  (slot.transform.childCount>=1&&slot.GetItemType()==item.Type&&slot.ISFilled()== false )
            {
                 return  slot;
            }
        }
         return  null ;
    }
Slot中调用的方法:
     public  void  StoreItem(Item item) //用于存储Item
    {
         // todo
    }

     public  Item.ItemType GetItemType() //得到当前物品槽存储的物品
    {
         //这里得到了相同的类型.我认为有问题.应该得到相同的Id才行
         return  transform.GetChild(0).GetComponent<ItemUI>().Item.Type;
         //返回这个物体的[0]子物体-下面的ItemUI组件的Item
         //这个Item已经在ItemUI里面被构造了. public Item Item { get; set; }
    }
     /*
    public Item GetItemId()
    {
        return transform.GetChild(0).GetComponent<ItemUI>().Item;
    }*/

     public  bool  ISFilled() //判断当前的物品槽是否满了
    {
        ItemUI itemUI = transform.GetChild(0).GetComponent<ItemUI>();
         return  itemUI.Amoint >= itemUI.Item.Capacity;
    }
在ItemUI中会要用到这个组件存储的Item和存储的数量,所以这里要构造:
     public  Item Item {  get ;  set ; } //这里用到了{get;set;}方法 所以这里的Item要大写
     public  int  Amoint {  get ;  set ; }
18.完善Slot中的存储功能.
首先在Solt中声明一个Gameobject,命名为itemprefab.将item拖进ItemPrefab中.

代码如下:
public  GameObject itemPrefab; //这里声明一个itemPrefab,用来实例化
    /*下面这方法用于存储item到slot中,这个方法只要完成存储功能就可以
    并不需要进行判断.因为调用这个方法的时候,已经完成了判断,调用这个方法的
    slot一定是可以存储的,所以下面的方法只要区分是让它数量+1,还是新实例化出
    来一个Item就可以了.*/
     public  void  StoreItem(Item item) //用于存储Item
    {
         if  (transform.childCount == 0) //如果是个空物品槽,实例化出来一个
        {
             //实例化出来一个itemPrefab.把这个itemPrefab作为Gameobject
            GameObject itemGameObject = Instantiate(itemPrefab)  as  GameObject;
             //把父类的位置和自身的位置重合
            itemGameObject.transform.SetParent(this.transform);
            itemGameObject.transform.localPosition = Vector3.zero;
             //这里他的父类和自身都是中心点设计,所以设置Vector3.zero(位置归零)
            itemGameObject.GetComponent<ItemUI>().SetItem(item);  //得到ItemUI组件,设置图片和数量.
        }
         else //如果有相同类型的物品,调用ItemUI中的AddMount()方法
        {
            transform.GetChild(0).GetComponent<ItemUI>().AddAmount();
            //这里不能直接得到该物体的组件,而是它子物体的组件.
        }
    }
这里调用了ItemUI里面的SetItem方法:
     public  void  SetItem(Item item,  int  amount = 1)
    {
         this .Item = item;
         this .Amount = amount;
         // todo Update UI
    }
     public  void  AddAmount( int  amount = 1)
    {
         this .Amount += amount;
         // todo Update UI
    }
19.测试捡起物品的功能
在Unity中添加一个空物体,这个空物体下面挂Player脚本.
public  class  Player  : MonoBehaviour {
        void  Update  () {
              //按键G得到物品
            if  (Input.GetKeyDown(KeyCode.G))
           {
                int  id = Random.Range(1, 2); //随机得到一个物品包含最小值不好喊最大值
               Knapsack.Instance.StoreItem(id);//存储得到的物品
           }
       }
}
在Knapsack下面创建一个单例模式
     #region  单例模式
     private  static  Knapsack _instance;
     public  static  Knapsack Instance
    {
         get
        {
             if  (_instance= null )
            {
                _instance = GameObject.Find("KnapsackPanel").GetComponent<Knapsack>();
            }
             return  _instance;
        }
    }
     #endregion
20.跟新UI
跟新UI只要跟新自身的图片,还有自身的数量显示更改,这里要更改这两个属性的话,需要在ItemUI中得到这2个组件.
     #region  UI组件
     //跟新UI包含图片和数量,要想跟新这图片和数量必须得到这2个组件
     private  Image itemImage;
     private  Text amountText;
     private  Image ItemImage
    {
         get
        {
             if  (itemImage ==  null )
            {
                itemImage = GetComponent<Image>();
            }
             return  itemImage;
        }
    }
     private  Text AmountText
    {
         get
        {
             if  (amountText ==  null )
            {
                amountText = GetComponentInChildren<Text>();
            }
             return  amountText;
        }
    }
     #endregion
     /*
    void Start()//这里不能用Start方法,因为这个item被实例化出来之后,马上就会执行下面的方法.strat方法来不及执行
    {
        //在start中得到这个图片和数量的组件
        itemImage = GetComponent<Image>();
        amountText = GetComponentInChildren<Text>();
    }
    */
     public  void  SetItem(Item item,  int  amount = 1)
    {
         this .Item = item;
         this .Amount = amount;
        ItemImage.sprite = Resources.Load<Sprite>(item.Sprite);
         //itemImage.sprite表示在itemImage下面的sprite组件.也就是这个组件的渲染图片
         //Resources.Load<a>(b);表示加载a类型的b路径的一个组件.
        AmountText.text = Amount.ToString();
         //表示让这个数量转化成字符串显示.
    }
     public  void  AddAmount( int  amount = 1)
    {
         this .Amount += amount;
        AmountText.text = Amount.ToString();
    }


21.ToolTip(提示框)的制作.
红色的四个角是锚点,用来定位面这个Text的缩放比例,如果他的锚点跟外面的Image一致的时候,里面的Text的大小会随着外面的父类的大小等比缩放.
绿色的这四个边点是用来写文字内容的.这个范围就是写字的范围.


这里我们做ToolTip的时候,要让背景随着文字框的大小来改变,文字框的大小要随着文字的多少来改变.
那么,这个时候我们就要把:
背景作为文字框的子物体,在文字框上加上:
Content Size Fitter 脚本
调成Preferred Size

这样就可以用文字的多少来控制这个框的大小了.
但是此时文本会被背景遮挡,这时候在背景下在建立一个Text组件就好了.
主要,这里的2个文本要保持一致.

这里的Content只是没有Bg
和ToolTip完全一致.
如果我们想让一个提示框出现在我们鼠标的右下角,我们需要让它的中心点到他的左上面.
那么我们就要修改Pivot的值(0,1)

Povit的值的取值范围是0-1之间.
(0,0) 的时候在左下角
(1,1) 的时候在右上角
22.ToolTip脚本 设计ToolTip的显示和隐藏
在ToolTip下面添加ToolTip脚本

ToolTip不需要交互,所有全部的Raycast Target全部取消勾选.
添加Canvas Group脚本.通过控制Alpha值来控制显示和隐藏.

public  class  ToolTip  : MonoBehaviour
{
     private  Text toolTipText; //背景中显示的文本
     private  Text contenText; //显示的文本内容.Ps:因为背景中的内容会被遮罩,所以在背景的上面还有一个文本.
     //第一个文本主要是为了控制背景的大小,第二个文本时为了显示内容.
     private  CanvasGroup canvasGroup; //控制ToolTip显示和隐藏的组件.
     private  float  targetAlpha = 0; //目标值.0为隐藏.1为显示
     public  float  smoothing = 3; //隐藏速度.
        void  Start  ()
       {
           toolTipText = transform.GetComponent<Text>();
        contentText = GameObject.Find("Content").GetComponent<Text>();//这里报错,不能得到该组件,空指针.
         //找到问题了,不要用transform.Find()方法,改用全局变量搜索方法.
           canvasGroup = GetComponent<CanvasGroup>();          
       }
        void  Update  () {
            if  (canvasGroup.alpha!=targetAlpha ) //当当前值不等于目标值的时候,进行插值运算,渐变到目标值
           {
               canvasGroup.alpha = Mathf.Lerp(canvasGroup.alpha, targetAlpha, smoothing * Time.deltaTime);
             //因为当前值不能达到目标值,为了节省内存,让差值小于一个数的时候就看作相等.
             if  (Mathf.Abs(canvasGroup.alpha-targetAlpha)<0.01f)
               {
                   canvasGroup.alpha = targetAlpha;
               }
           }              
       }
        void  Show( string  text) //显示tooltip,text为显示的文本信息.
            {
                toolTipText.text = text;
                contenText.text = text;
                targetAlpha = 1;
            }

     void  Hide() //隐藏tooltip
    {
        targetAlpha = 0;
    }
}

23.用InventoryManager管理ToolTip的显示和隐藏.
在InventoryManager中增加下面代码.
     private  ToolTip toolTip; //得到ToolTip组件.
     void  Start ()
    {
        ParseItemJson();//开始解析Json
        toolTip = GameObject.FindObjectOfType<ToolTip>();
         //因为只有一个ToolTip类型,所以用FindObjectOfType来查找.
    }
     void  ShowToolTip( string  content)
    {
        toolTip.Show(content);
    }
     private  void  HideToolTip()
    {
        toolTip.Hide();
    }

24.在ToolTip上显示物品的信息.
当鼠标移动到slot上显示里面存储物品的信息.所以这个代码要放在slot上.
首先需要2个接口.IPointEnterHandle  IPointExitHandle
并且实现这2个接口.
public  class  Slot  : MonoBehaviour,IPointerEnterHandler,IPointerExitHandler
{
x
x
x
x
x
    //当鼠标移出物品槽的时候.
     public  void  OnPointerExit(PointerEventData eventData)
    {
         if  (transform.childCount>0)
        {
            InventoryManager.Instance.HideToolTip();
        }
    }
     //当鼠标放在物品槽上的时候.
     public  void  OnPointerEnter(PointerEventData eventData)
    {
         if  (transform.childCount>0)
        {
             string  content = transform.GetChild(0).GetComponent<ItemUI>().Item.GetToolTipText();
            //通过这个物品槽怎么得到显示内容:
             //得到这个物品槽的子物体,后再得到子物体的ItemUI组件,然后再得到这个组件的Item,最后调用这个GetToolTipText方法得到显示信息.
            InventoryManager.Instance.ShowToolTip(content);
        }
    }
}
这里ToolTip显示的内容 应该在Item上显示出来.
所以这应该在Item脚本上做功能.
     //返回物品的信息,这里用虚方法.这是因为这里是父类,子类不都一样,还会重写很多.
     public  virtual  string  GetToolTipText()    
    {
         return  Name; // todo
    }
25.ToolTip的位置跟随.
ToolTip在InventoryManager中管理.
     private  bool  isToolTipShow =  false ; // //标志位,用来查看是否显示提示信息. 表示默认ToolTip不显示.
     private  Canvas canvas; //得到画布

     void  Start ()
    {
         ParseItemJson(); //开始解析Json
         toolTip = GameObject.FindObjectOfType<ToolTip>();
         //因为只有一个ToolTip类型,所以用FindObjectOfType来查找.
        canvas = GameObject.Find("Canvas").GetComponent<Canvas>();
    }
      void  Update ()
    {
         if  (isToolTipShow)
        {
            Vector2 position;
             //下面的函数用来得到鼠标的位置,并返回到position上.
            RectTransformUtility.ScreenPointToLocalPointInRectangle
                (canvas.transform  as  RectTransform, Input.mousePosition,  null ,  out  position);
            toolTip.SetLocalPosition(position+ new  Vector2(10,-10));
        }
    }
     public  void  ShowToolTip( string  content)
    {
        isToolTipShow =  true ; //标志位,用来查看是否显示提示信息.
        toolTip.Show(content);
    }
     public    void  HideToolTip()
    {
        isToolTipShow =  false ;//标志位,用来查看是否显示提示信息.
        toolTip.Hide();
    }
在给ToolTip位置的时候,要在ToolTip里面做这个功能.
     public  void  SetLocalPosition(Vector3 position)
    {
         this .transform.localPosition = position;
         //注意!!!这里是localPosition 而不是position.
    }
26.完善InventoryManager里面的Item.ItemType.Equipment
  switch  (type) //特性类独有的属性在switch里面处理.
            {
                 case  Item.ItemType.Consumable:
                     //Consumable特殊属性就是hp和mp
                     int  hp = ( int ) temp[ "hp" ].n;
                     int  mp = ( int ) temp[ "mp" ].n;
                     //创建一个新的Consumable,注意这里是创建一个new Consumable,不是new Item
                    item= new  Consumable(id,name,type,quality,description,capacity,buyPrice,sellPrice,sprite,hp,mp);
                     break ;
                 case  Item.ItemType.Equipment:
                     int  strength = ( int ) temp[ "strength" ].n;
                     int  intellect = ( int ) temp[ "intellect" ].n;
                     int  agility = ( int ) temp[ "agility" ].n;
                     int  stamina = ( int ) temp[ "stamina" ].n;
                    EquipMent.EquipmentType equipType =
                        (EquipMent.EquipmentType) System.Enum.Parse(typeof(EquipMent.EquipmentType),
                            temp["equipType"].str);
                    item= new  EquipMent(id, name, type, quality, description, capacity, buyPrice, sellPrice, sprite,strength,intellect,agility,stamina, equipType);
                     break ;
                 case  Item.ItemType.Weapon:
                     int  damage = ( int )temp[ "damage" ].n;
                    Weapon.WeaponType wpType =
                        (Weapon.WeaponType) System.Enum.Parse(typeof(Weapon.WeaponType), temp["weaponType"].str);
                    item =  new  Weapon(id, name, type, quality, description, capacity, buyPrice, sellPrice, sprite, damage, wpType);
                     break ;
                 case  Item.ItemType.Material:
                    item= new  Item(id,name,type,quality,description,capacity,buyPrice,sellPrice,sprite);
                     break ;
           }
27.完善描述信息.
在Item里面GetToolTipText()方法.
这里要记得勾选富文本,不然<color=red></red>这样的富文本不会被解析.
同理,字体大小也可以用<size=xx></size>来解析.

     public  virtual  string  GetToolTipText()
    {
         string  color =  "" ;
         switch  (Quality)
        {
             case  ItemQuality.Common:
                color =  "white" ;
                 break ;
             case  ItemQuality.Uncommon:
                color =  "lime" ;
                 break ;
             case  ItemQuality.Rare:
                color =  "navy" ;
                 break ;
             case  ItemQuality.Epic:
                color =  "magenta" ;
                 break ;
             case  ItemQuality.Legendary:
                color =  "orange" ;
                 break ;
             case  ItemQuality.Artifact:
                color =  "red" ;
                 break ;
            }
          string  text =  string .Format( "<color= {4} > {0} </color> \n 出售价格: {1}  购买价格: {2} \n {3} " ,
            Name,SellPrice,BuyPrice,Description,color);
         //这里用到了string.Formate("",);格式
         return  text;
    }
这时候得到的效果为:

28.重写GetToolTipText方法
Consumable
     //重写父类的GetToolTipText
     public  override  string  GetToolTipText()
    {
         string  text =  base .GetToolTipText(); //得到基于父类的文本信息
         string  newTest =  string .Format( " {0} \n <color=white>加血: {1}  加蓝: {2} </color>" , text, HP, MP);
         return  newTest;
    }
EquipMent
这里的EquipType是枚举类型所以要用switch语句
     //重写父类的GetToolTipText
     public  override  string  GetToolTipText()
    {
         string  text =  base .GetToolTipText();
         string  eqType =  "" ;
         switch  (EquipType)
        {
             case  EquipmentType.Head:
                eqType =  "头部" ;
                 break ;
             case  EquipmentType.Neck:
                eqType =  "颈部" ;
                 break ;
             case  EquipmentType.Chest:
                eqType =  "胸部" ;
                 break ;
             case  EquipmentType.Ring:
                eqType =  "手指" ;
                 break ;
             case  EquipmentType.Leg:
                eqType =  "腿部" ;
                 break ;
             case  EquipmentType.Bracer:
                eqType =  "手腕" ;
                 break ;
             case  EquipmentType.Shoulder:
                eqType =  "肩膀" ;
                 break ;
             case  EquipmentType.Belt:
                eqType =  "腰部" ;
                 break ;
             case  EquipmentType.OffHand:
                eqType =  "副手" ;
                 break ;
        }
         string  newText =  string .Format( " {0} \n 装备类型: {5} 装备 \n <color=white>力量: {1} \n 智力: {2} \n 敏捷: {3} \n 体力: {4} </color>" , text, Stregth, Intellect, Agility, Stamina,eqType);
         return  newText;
    }
Weapon
     //重写GetToolTipText()
     public  override  string  GetToolTipText()
    {
         string  wpType =  "" ;
         switch  (WpType)
        {
             case  WeaponType.MainHand:
                wpType =  "主手武器" ;
                 break ;
             case  WeaponType.OffHand:
                wpType =  "副手武器" ;
                 break ;
        }
         string  text=  base .GetToolTipText();
         string  newText =  string .Format( " {2} \n 武器类型: {0} \n 伤害值: {1} " , wpType, Damage, text);
         return  newText;
    }

到这里背包系统的雏形基本完成.
下一笔记继续分析物品的移动,丢弃等.
附录:用到的一些英文名:

Inventory 存货
Knapsack  背包
Chest       箱子

都有的信息
id type name quality description maxcapacity buyprice sellprice
消耗品 装备 武器 材料
Consumable Equipment Weapon Material

Quality
    Common       一般         white 白色
    Uncomman    不一般        lime 绿黄色
    Rare         稀有         navy 深蓝色
    Epic          史诗        magenta 品红
    Legendary     传说        orange 橘色
    Artifact      远古        red 红色

消耗品
hp mp

装备
strength,intellect,agility,stamina
力量   智力     敏捷    体力

Head    头
Neck    脖子
Chest  胸部
Ring    戒指
Leg  腿
Bracer 护腕
Boots  靴子
Trinket 饰品
shoulder  肩膀
belt 腰带

武器
Mainhand    主手
Offhand      副手
damage

猜你喜欢

转载自blog.csdn.net/allen_zgd/article/details/79491026