背包系統,顧名思義,就是像書包一樣存儲玩家角色所需的各種物品的系統。我姑且這樣描述:一個背包系統有許多物品欄組成,一個物品欄存放一種物品。如下圖的背包系統就有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)另外需要一個腳本類來實現物品動態增加的功能。也是從物品欄為空時、不為空的角度來考慮。到底是從無到有還是直接對已有的物品增加數量,就看各位看官的能耐啦。