背包系统,顾名思义,就是像书包一样存储玩家角色所需的各种物品的系统。我姑且这样描述:一个背包系统有许多物品栏组成,一个物品栏存放一种物品。如下图的背包系统就有16个物品栏。我们需要实现一种简易功能:就是每当一定时机生成一种物品,将物品放到背包系统的界面。再进一步,如果背包中存在的物品和即将放入的物品是同一种时,只更改该物品的数量;如果背包中不存在即将放入的物品,则在背包中增加该物品,放入的位置是按空位置依次放入,不能随机乱放。如下图所示,每个物品栏右下角为物品的数量。
现在写这篇文章的时候,逻辑我还没有捋的很清晰。等我写完,不止我,各位看官相信也会不在感到迷茫了。我们的背包系统是16个单元格组成的,cell 1~16。实现背包系统的底层支持,其实得益于NGUI的UIDragDropItem脚本,通过类名我们可以明白这就是关于拖拽功能的一个脚本,我们需要做的就是要继承该类,重写一个事件处理函数OnDragDropRelease(GameObject surface),该函数的主要作用是:当我们拖拽物品放下(Release)时,此函数会被触发。但是,
(1)被拖拽的物品必须添加这样一种脚本,脚本继承自UIDragDropItem脚本类。
(2)被拖拽的物品必须添加碰撞体,NGUI交互必须添加碰撞体。
(3)物品所要放置的UI对象也必须加上碰撞体,理由如上相同。
所以,cell1~cell16这16个UI游戏对象都必须添加上碰撞体。它们的功能都是相同的,都是接受物品拖放的。当多个游戏对象的功能相同时,U3d告诉我们,最好把他们保存为Prefab,所以cell预设体也就产生了。
而物品的功能我们也能够很自然的想到了,就是能够实现拖拽功能,并且能够接收拖拽释放后的事件,做出一些事件处理。我们把物品保存也保存为预设体,KnapsackItem预设体。OnDragDropRelease(GameObject surface)函数是实现的细节。。。。。。将物品放入物品栏的中心;当一个物品放入另一个含有其它物品的物品栏中时,两种物品对换;物品栏中的物品增加时,物品的下标(数量)实时变更。还有一个我自己添加的功能,感觉没什么用处,就是将一个物品拖入另一个含有相同物品的物品栏时,两个物品合并,物品下标(数量)增加。
下图为背包系统的层级视图和对应的Scene视图的内容。
该预设体包含了这样一个脚本,KnapsackItem.cs。该脚本可以实现将物品放入背包,并且当拖入的目标对象中有其它物品时,将两个物品互换。此时,我们就实现了将一个物品栏中的物品放入其它物品栏的功能。
以上只是背包系统的一种功能,将背包中已有的物品更换物品栏,或是交换两个物品栏中的物品。我们还应该增加一个功能:增加物品。在这里,我们无法像在具体游戏那样增加物品,所以我们采用每按一个按键,物品栏中就会随机多出一个物品的方法(测试方法)。
我们想一想增加物品应该放在哪一个游戏对象的脚本中呢?
增加物品,应该是物品栏增加物品,所以,此函数应该包含在象征着物品栏的游戏对象上挂在的脚本的一个方法。我们一调用该方法,就会在物品栏中按照一定规则增加物品。脚本名我们定义为Knapsack.cs脚本,该脚本定义一个public类型的方法:PickUp()。我们慢慢思考,一点点扩展功能。首先,最简单的思路就是,我们每按下X键就会忘背包系统中增加一个物品,增加物品的物品栏是有优先级的,我们认为cell1的优先级最高。每执行一次该函数,就应该从cell1开始遍历到cell16,当物品栏为空时,我们就忘该物品栏增加一个物品,增加物品的函数是NGUI提供给我们的,NGUITools.AddChild(itemParent.gameObject, item);我感觉该函数的作用应该就是使用GameObject的Instantiate函数创建出一个item,并且将item作为第一个参数itemParent.gameObject游戏对象的子对象,仅此而已。我们要记得改换物品的sprite,以及break退出该循环以保证这是我们一次增加物品,如果没有break,所有为空的物品栏都会填充上item,这并不是我们想要看到的结果。
到此为止,我们这个背包系统是不是感觉非常的蛋疼,因为很多相同的物品放在了不同的物品栏中。我们想要得到的结果是每个物品栏存放相同的物品,物品需要有数量标识。
下面让大家看看我们实现的比较蛋疼的背包系统吧。
牢骚就此打住,我们还是想一想应该如何改进吧。在向物品栏中增加item之前,我们必须判断物品栏中是不是有与将要添加的物品相同的物品,如果有的话,直接增加其数量值即可,也省了增加物品的不必要的开销了。一个小技巧就是在PickUp函数中增加一个标识变量 bool hasTheSame,一开始该变量为false,一旦物品栏中有相同物品时,执行增加物品数量的操作,并且hasTheSame变为true。将刚刚我们实现的往空物品栏中增加物品的代码放入 if(!hasTheSame){ }中就好了。下面我附上Knapsack.cs和KnapsackItem.cs脚本,让大家看一下,有不足之处请大家指正。
1 using UnityEngine; 2 using System.Collections; 3 4 public class Knapsack : MonoBehaviour { 5 public GameObject item; 6 public string[] itemNames; 7 // Use this for initialization 8 void Start () { 9 10 } 11 12 // Update is called once per frame 13 void Update () { 14 if (Input.GetKeyDown(KeyCode.X)) 15 { 16 PickUp(); 17 } 18 } 19 /// <summary> 20 /// 该函数将获取一个物品 21 /// 依次遍历各个单元格,如果要加入的物品在物品栏中已经存在,则只需要在将物品栏中存在的物品的数量增加即可 22 /// 如果要加入的物品不存在的话,添加该物品 23 /// 通过一个标志位来判断属于上面哪种情况 24 /// </summary> 25 public void PickUp() 26 { 27 bool hasTheSame = false; 28 int index = Random.Range(0, 3); 29 30 //for (int i = 0; i < transform.childCount; i++) 31 //{ 32 // //itemParent表示物品要摆放的窗口 33 // Transform itemParent = transform.GetChild(i); 34 35 // //如果要加入的物品在物品栏中已经存在,则只需要在将物品栏中存在的物品的数量增加即可 36 // if (itemParent.childCount > 0) 37 // { 38 // //print(itemParent.GetComponent<UISprite>().spriteName); 39 // //print(itemParent.GetComponentInChildren<UISprite>().spriteName); 40 // if (itemParent.GetChild(0).GetComponent<UISprite>().spriteName == itemNames[index]) 41 // { 42 // //itemParent.GetComponentInChildren<KnapsackItem>().AddCount(1); 43 // itemParent.GetChild(0).GetComponent<KnapsackItem>().AddCount(1); 44 // hasTheSame = true; 45 // break; 46 // } 47 // } 48 //} 49 //如果物品栏没有相同的物品 50 if (!hasTheSame) 51 { 52 for (int i = 0; i < transform.childCount; i++) 53 { 54 //itemParent表示物品要摆放的窗口 55 Transform itemParent = transform.GetChild(i); 56 57 if (itemParent.childCount == 0) 58 { 59 //向一个游戏对象中添加子对象,该子对象是预设体Prefab 60 GameObject go = NGUITools.AddChild(itemParent.gameObject, item); 61 go.GetComponent<UISprite>().spriteName = itemNames[index]; 62 go.transform.localPosition = Vector3.zero; 63 64 break; 65 } 66 } 67 } 68 69 } 70 }
1 using UnityEngine; 2 using System.Collections; 3 4 //使用继承UIDragDropItem类 5 public class KnapsackItem : UIDragDropItem 6 { 7 8 public UISprite selfSprite; 9 public UILabel numberLabel; 10 int count = 1; 11 12 public void AddCount(int value) 13 { 14 count += value; 15 numberLabel.text = count.ToString(); 16 } 17 18 protected override void OnDragDropRelease(GameObject surface) 19 { 20 base.OnDragDropRelease(surface); 21 //如果拖拽的目标对象没有子对象,则直接变为目标对象的子物体,在目标对象的坐标系中归零 22 if (surface.tag == "cell") 23 { 24 transform.parent = surface.transform; 25 transform.localPosition = Vector3.zero; 26 //如果拖拽的目标对象为Cell的子对象,因为子对象的depth大,在上面,并且有Collider组件 27 //此时应该交换两个物品item 28 }else if(surface.tag == "item"){ 29 //合并两个相同的物品 30 if (transform.GetComponent<UISprite>().spriteName == surface.GetComponent<UISprite>().spriteName) 31 { 32 transform.parent = surface.transform.parent.transform; //surface表示物品 33 transform.localPosition = Vector3.zero; 34 Destroy(surface); 35 AddCount(1); 36 } 37 else 38 { 39 Transform newParent = surface.transform.parent; //新旧父亲是相对于拖动的物品的。 40 Transform oldParent = transform.parent; 41 surface.transform.parent = oldParent; 42 surface.transform.localPosition = Vector3.zero; 43 transform.parent = newParent; 44 transform.localPosition = Vector3.zero; 45 } 46 } 47 } 48 }
最后,总结一下背包系统的要点:
(1)需要拖拽的物品需要添加继承自UIDragDropItem.cs的脚本类,并且需要重写OnDragDropRelease()函数。需要实现的功能为:物品栏为空时物品拖入物品栏中心位置、物品栏中已有物品并且与需要拖拽的物品相同时增加物品数量、物品栏中已有物品并且与需要拖拽的物品不相同时交换两个物品。
(2)物品对象和物品栏对象必须加上碰撞体组件才能实现拖拽功能。
(3)另外需要一个脚本类来实现物品动态增加的功能。也是从物品栏为空时、不为空的角度来考虑。到底是从无到有还是直接对已有的物品增加数量,就看各位看官的能耐啦。