在背包系统中有许多的物品,每一件物品都有很多的属性,例如:
[ { "id": 1, "name": "血瓶", "type": "Consumable", "quality": "Common", "description": "这个是用来加血的", "capacity": 10, "buyPrice": 10, "sellPrice": 5, "hp": 10, "mp": 0, "sprite": "Sprites/Items/hp" }, { ...... } ]这是一个JSON文件,里面只有一个“血瓶”的物件以及它的各项属性。我们可以通过解析JSON文件获取这个物件的名称及其属性。这样就不用对每个物体就进行单独的书写,造成代码的复杂。
在Unity3D中可以使用JSON官网(json.org)的解析包,也可以使用Asset Store的JSON解析包。我这里使用的是后者,其名就是“JSON”。在这个包里,其作者对JSON的一些解析进行了重写。
先new一个JSONObject的对象:JSONObject j = new JSONObject(JSONObject.Type.OBJECT)
之后就可以对JSON文件中的内容进行解析读取,例如上面那个例子:
void ParseItemJson(){ TextAsset itemText = Resources.Load<TextAsset>("Item");//Item就是我在U3D工程中JSON文件的命名 string itemsJson = itemText.text;//物品信息的JSON格式 JSONObject j = new JSONObject(itemsJson); /* 若j包含多个物品属性,就要用: foreach (JSONObject temp in j.list)进行循环遍历 */ string typeStr = j["type"].str;//这一项可能是多个物品的公共属性,比如物品的品质等 int id = (int)(temp["id"].n);//这里我强转了类型,因为在JSON中作者用的float,而我的json文件使用的整型数字。 string name = temp["name"].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; }xml和txt就不多说了,json的解析明显要比这两者更轻量。再有,在开发阶段虽然可以不怎么考虑优化问题,但还是建议尽量选择json文件来存取数据和存放相应的游戏配置。
在背包系统中或者其他系统中,我们经常要使用到:对某物体的信息进行自动扩充时,Image的大小总是与Text的大小相适应。
很多时候为了在一张Image上显示文本信息,我们是将Text作为Image的子物体进行使用;为了实现自适应文本框,我们需要通过以下步骤:
①创建一个文本(Text),文本下载创建一个文本(Text)和一个图片(Image);
②两个子物品都设置为锚点适应父物品;
③同时给两个Text添加Content Size Fitter组件,设置Horizontal Fit和Vertical Fit为Preferred Size;
④一定要同步两个文本框内容的显示:父Text只是为了控制显示区域,真正显示内容是在子Text上。
在二中我们解决了文本框的自适应问题,我们现在要解决的就是当移动鼠标至物体身上时,如何显示出物体的信息?
这里我使用的是ScreenPointToLocalPointInRectangle函数,它有四个参数:
①RectTransform:子节点相对于父节点的UGUI坐标;
②ScreenPoint:屏幕坐标,这里也就是Input.mousePosition;
④Camera:事件相机,如果canvas的渲染模式是Screen Space-camera或者world Space,就从canvas中得到渲染相机坐标。如果是Screen Space-Overlay,这里就可以传一个null;
⑤Out Position:传出子节点在父节点的局部坐标
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, Input.mousePosition, null, out position);我们这里采用的是单击鼠标就能进行物体的选出。所以,这里我们使用的是IPointDownHandler(关于鼠标的点击事件的接口有:IPointClickHandler、IPointDownHandler、IPointEnterHandler、IPointExitHandler、IPointUpHandler)
IPointDownHandler的接口函数是:public void OnPointerDown(PointerEventData eventData)。
鼠标按下的时候,根据物品数量的不同情况再分写代码。
以上只是让鼠标能获取到物品本身,还不能让物体跟随鼠标移动
在三中,我们知道ScreenPointToLocalPointInRectangle函数的输出是一个物体的坐标,所以结合此函数:
/*在物品UI中*/ public void SetLocalPosition(Vector3 position){ transform.localPosition = position; } /*在背包管理的脚本中,用isPckedItem表示鼠标获取到了物体,那么*/ if(isPickedItem){ Vector2 position; RecTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, Input.mousePosition, null, out position); pickedItem.SetLocalPosition(position); }这样设置之后,鼠标所点击的物品就会跟随鼠标移动。
①保存:我们所采取的方法是将所有的物品信息保存在一个string里面,并通过PlayerPrefs.SetString()函数将保存的string存储在本地,具体如下:
public void SaveInventory(){ StringBuilder sb = new StringBuilder(); foreach(Slot slot in slotList){//遍历物品格里面的信息 if(slot.transform.childCount > 0){ ItemUI itemUI = slot.transform.GetChild(0).GetComponent<ItemUI>(); sb.Append(itemUI.Item.ID + ","+itemUI.Amount+"-"); } else{ sb.Append("0-"); } } PlayerPrefs.SetString(this.gameObject.name, sb.ToString()); }PlayerPrefs.SetString(a,b)有两个参数:参数a表示存储数据的名称,参数b便是具体的存储的数值。
②读取:先使用PlayerPrefs.HasKey()来判断数据是否存在,如果存在,再用PlayerPrefs.GetString(a)来读取,读取得结果也会是一个string,然后对读取到的string进行初步分解(因为我们在保存的时候使用的”-“来进行不同信息间的分割,所以分解时也以”-“为标志位),将分解的信息存储在一个数组中,再遍历这个数组,根据上面的保存方式,这个数组里面有两种信息:0或者”itemUI.Item.ID + “,”+itemUI.Amount“,所以对数组遍历进行内容判断:如果不是0,那么再对内容拆分(","为标志位),这个内容数组的第一项就是ID,第二项就是Amount。再根据这两个信息对slotList[i](第i个物品格)进行物品信息的更新
public void LoadInventory(){ if (PlayerPrefs.HasKey(this.gameObject.name) == false) return; string str = PlayerPrefs.GetString(this.gameObject.name); string[] itemArray = str.Split('-'); for (int i = 0; i < itemArray.Length-1; i++){ string itemStr = itemArray[i]; if (itemStr != "0"){ string[] temp = itemStr.Split(','); int id = int.Parse(temp[0]); Item item = InventoryManager.Instance.GetItemById(id); int amount = int.Parse(temp[1]); for (int j = 0; j < amount; j++){ slotList[i].StoreItem(item); } } } }注意:我们这里的保存和读取只是对某个子系统物品格的保存与读取,比如背包子系统、仓库子系统、角色身体等,所以保存时要对每个子系统的单例模式进行操作。读取时就不需要了,只是对保存的名字的文件进行读取。
当我设置生成动画后的物品的Scale是1时,生成动画结束后的物品Scale要比没有动画时的大,这是为什么呢?
通过关闭动画后发现,生成后的物品的Scale并不是1,尽管我们设置的预制体的Scale是1。
原因是我们实例化出来的物品是放在根目录下的,设置父子关系和位置的时候才会放在父物体之下,而最大的物体Canvas是有Scale限制的,Canvas的子物体又会去限制孙代物体的Scale。因此,最终我们得到的实例化物体的Scale就不再是1了。
官方的一个方法
EventSystem.current.IsPointerOverGameObject 它的参数是pointerId它不仅仅被用于PC端鼠标对UI的控制,还可用于移动端的触控。这就涉及带不带参数了。
当不带参数时,IsPointerOverGameObject(),此时默认的是pointerId = -1,指向鼠标左键
当代参数时(-1以外),就是进行触控。
需要注意的是:
IsPointerOverGameObject应该与OnMouseDown() 或 Input.GetMouseButtonDown(0) 或Input.GetTouch(0).phase == TouchPhase.Began一起使用。同时这是一个bool类型
以PC端为例:
//我们经常这样使用: UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject(-1) == true/false; //true,意味着你的鼠标上是有Item的,反之false在四中我们为了将物品从箱子中取出,用了IPointDownHandler类,它会自动生成它的接口:void OnPointDown()。若直接在该函数里面进行内容的书写,那么在实际应用上,无论是点击鼠标左键还是右键,还是中键滑轮 ,都会获取到物品,这显然不是我们所想的。需要在里面加入判断语句:
public virtual void OnPointerDown(PointerEventDatav eventData){ if(eventData.button == PointerEventData.InputButton.Left) ......; /*如果反向设置就是: if(eventData.button != PointerEventData.InputButton.Left) return; 就直接return. */ }那么我们经常会使用到对一物体单击某个键位,带物体自动穿戴或者自动使用,这种怎么处理呢?以右键为例:
①在OnPointerDown函数中对右键设置键位判断:
if(eventData.button == PointerEventData.InputButton.right)
②根据物体与角色身体部位的关系、物体数量关系进行逻辑编码。
从角色上脱掉也是一样的,反过来操作而已。
