Related Posts Plugin for WordPress, Blogger...

5-14 Discard Item And Control Panel Display

今天要來教大家製作『丟棄物品』和『控制介面顯示』的功能,將物品用滑鼠移動到背包外面並點擊滑鼠左鍵,該物品就會被丟棄,以及使用快捷鍵和介面右上角的叉叉按鈕,就可以開關介面。

首先,在InventorySystem.cs的Update中,額外增加下列的程式碼,使用EventSystem的IsPointerOverGameObject方法,可以判斷滑鼠左鍵有沒有點擊到GameObject,若沒有,則將PickedItem隱藏,並將isPickedItem設為false。下次滑鼠點擊背包的其他物體時,PickedItem資訊會被重整,相當於上一個狀態的物體被丟棄。

InventorySystem.cs的完整程式碼如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.UI;
using UnityEngine.EventSystems;

namespace RPG.Inventory{
 public class InventorySystem : MonoBehaviour {

  [SerializeField] ToolTip toolTip;
  [SerializeField] ItemUI pickedItem;

  public bool isPickedItem = false;
  ItemList itemList;
  Slot[] slotList;

  void Start () {
   ParseItemsJson ();

   slotList = GetComponentsInChildren ();
  }

  void Update(){
   // 測試程式碼,手動生成物品
   if (Input.GetKeyDown (KeyCode.G)) {
    StoreItem (Random.Range(1,3));
   }

   if (isPickedItem) {
    // 將滑鼠座標轉換成Canvas上的座標
    Vector2 position;
    Canvas canvas = GetComponentInParent ();
    RectTransformUtility.ScreenPointToLocalPointInRectangle (
     canvas.transform as RectTransform,
     Input.mousePosition,
     null,
     out position);
    pickedItem.SetLocalPosition (position);
   }

   // 處理物品丟棄
   // IsPointerOverGameObject(-1)判斷滑鼠左鍵是否有碰到GameObject
   if (pickedItem && Input.GetMouseButtonDown (0) 
    && EventSystem.current.IsPointerOverGameObject(-1) == false) {
    isPickedItem = false;
    pickedItem.Hide ();
   }
  }

  public void PickupItem(Item item, int amount){
   pickedItem.SetItem (item, amount);
   pickedItem.Show ();
   toolTip.Hide ();
   isPickedItem = true;
  }

  public void ReducePickedItem(int amount){
   pickedItem.ReduceAmount(amount);
   if (pickedItem.Amount <= 0) {
    isPickedItem = false;
    pickedItem.Hide ();
   }
  }

  public ItemUI GetPickedItem(){
   return pickedItem;
  }

  public void ShowToolTip(string content){
   toolTip.Show (content);
  }

  public void HideToolTip(){
   toolTip.Hide ();
  }

  public bool StoreItem(int id){
   Item item = GetItemByID (id);
   return StoreItem (item);
  }

  public bool StoreItem(Item item){
   if (item == null) {
    return false;
   }

   // 若物品的儲存容量為1,則將該物品直接放進空的Slot
   if (item.Capacity == 1) {
    return StoreItemInEmptySlot (item);
   }
   // 將相同的Item放在同一個Slot
   return StoreItemInSameSlot(item);
  }

  private bool StoreItemInSameSlot(Item item){
   foreach (Slot slot in slotList) {
    if (slot.transform.childCount >= 1 && slot.GetItemID () == item.ID &&
     slot.IsFilled () == false) {
     // 將新Item與同一個Item放在一起
     slot.StoreItem (item);
     return true;
    } 
   }
   // 若背包內不存在相同的Item,則放進空的Slot
   return StoreItemInEmptySlot (item);
  }

  private bool StoreItemInEmptySlot(Item item){
   foreach (Slot slot in slotList) {
    if (slot.transform.childCount == 0) {
     // 將Item存進該Slot
     slot.StoreItem (item);
     return true;
    }
   }
   Debug.LogError ("No Empty Slot.");
   return false;
  }

  private Item GetItemByID(int id){
   foreach (Item entity in itemList.ConsumableEntityList) {
    if (entity.ID == id) {
     return entity;
    } 
   }
   foreach (Item entity in itemList.EquipmentEntityList) {
    if (entity.ID == id) {
     return entity;
    } 
   }
   foreach (Item entity in itemList.MaterialEntityList) {
    if (entity.ID == id) {
     return entity;
    } 
   }
   return null;
  }

  private void ParseItemsJson(){
   // 從Resource資料夾中讀取Items.json
   TextAsset json = Resources.Load ("Items");
   // 解析Json格式
   itemList = JsonUtility.FromJson (json.text);
   // 使用foreach迴圈讀取List中的Item資料
   // TODO 讀取Json有Bug
  }
 }
}

新增一個Script名為Panel.cs,這個腳本要用來控制介面的隱藏和顯示。

Panel.cs的完整程式碼如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Panel : MonoBehaviour {

 [SerializeField] float panelAlpha = 1;
 [SerializeField] float smoothAlphaMultiplier = 4;
 [SerializeField] KeyCode hotKey;

 CanvasGroup canvasGroup;

 void Start () {
  canvasGroup = GetComponent ();
 }
 
 void Update () {
  if (Input.GetKeyDown (hotKey)) {
   DisplaySwitch ();
  }

  if (canvasGroup.alpha != panelAlpha) {
   // 用Lerp函數控制Alpha的漸變
   canvasGroup.alpha = Mathf.Lerp (canvasGroup.alpha, panelAlpha, Time.deltaTime * smoothAlphaMultiplier);
   if (Mathf.Abs (canvasGroup.alpha - panelAlpha) < 0.01f) {
    canvasGroup.alpha = panelAlpha;
   }
  }
 }

 public void Show(){
  canvasGroup.blocksRaycasts = true;
  panelAlpha = 1;
 }

 public void Hide(){
  canvasGroup.blocksRaycasts = false;
  panelAlpha = 0;
 }

 public void DisplaySwitch(){
  if (panelAlpha == 1) {
   Hide ();
  } else {
   Show ();
  }
 }
}



撰寫完成後,請在Chest Panel的Inspector視窗新增Canvas Group組件。

並新增Panel.cs,Hot Key的部分請大家選擇自己喜歡的快捷鍵,此處我選擇 C。

同理,在Knapsack Panel中也要新增Canvas Group組件和Panel.cs,此處的HotKey我選擇K。

接著,選擇Knapsack Panel底下的Close Btn。

在Inspector視窗中應該能看見之前新增的Button組件,在On Click()的地方應會顯示List is Empty,請大家按下『+』按鈕。

然後將Knapsack Panel拖曳到小框框中,如下圖。接著在No Function的地方選擇Panel/Hide()。

同理,在Chest Panel底下的Close Btn也要新增事件,只不過請注意這邊要拖曳的是Chest Panel,No Function的部分也選擇Panel/Hide()。

然後,執行遊戲看看,按下右上角的叉叉按鈕就可以關閉介面了。

按下快捷鍵也同樣可以關閉介面,再按一次快捷鍵則會打開介面。

接著調整一個小問題,我們在之前的文章中,沒有處理『當滑鼠抓著的物體與Slot中的物體不是同一個時,要將抓著的物體與Slot的交換』。所以,我們在Slot.cs中,如下圖撰寫部分程式碼改善。

Slot.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

namespace RPG.Inventory{
 public class Slot : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerDownHandler {

  [SerializeField] GameObject itemPrefab;

  public void StoreItem(Item item){
   // 若Slot底下沒有Item,則Instantiate一個
   if (transform.childCount == 0) {
    GameObject itemObject = Instantiate (itemPrefab, transform);
    itemObject.GetComponentInChildren ().SetItem (item);
   } else {
    // 已存在該Item,所以增加數量即可
    transform.GetComponentInChildren ().AddAmount ();
   }
  }

  public int GetItemID(){
   return transform.GetComponentInChildren ().Item.ID;
  }

  public bool IsFilled(){
   ItemUI itemUI = transform.GetComponentInChildren ();
   return itemUI.Amount >= itemUI.Item.Capacity;
  }

  public void OnPointerEnter(PointerEventData eventData){
   if (transform.childCount > 0) {
    Item item = GetComponentInChildren ().Item;
    GetComponentInParent ().ShowToolTip (item.GetToolTipText ());
   }
  }

  public void OnPointerExit(PointerEventData eventData){
   GetComponentInParent ().HideToolTip ();
  }

  public void OnPointerDown(PointerEventData eventData){
   InventorySystem invetorySystem = GetComponentInParent ();
   // Slot內是否已經有物體
   if (transform.childCount > 0) {
    // 取得目前滑鼠點擊Slot的ItemUI
    ItemUI clickedItemUI = GetComponentInChildren ();
    // 當滑鼠沒有Item時
    if (invetorySystem.isPickedItem == false) {
     if (Input.GetKey (KeyCode.Z)) {
      // 壓住Z鍵時,只會取出一半的數量
      PickupHalfOfSlotItem(clickedItemUI, invetorySystem);
     } else {
      // 取出全部
      PickupAllSlotItem (clickedItemUI, invetorySystem);
     }
    // 當滑鼠有Item時
    } else if (invetorySystem.isPickedItem == true) {
     // 當放入Item的是同一個時
     if (clickedItemUI.Item.ID == invetorySystem.GetPickedItem ().Item.ID) {
      if (Input.GetKey (KeyCode.Z)) {
       // 壓住Z鍵時,只會一個一個的放入物體
       PutInOneItem (invetorySystem);
      } else {
       // 放入所有物體
       PutInAllItem (clickedItemUI, invetorySystem);
      }
     } else {
      // 當放入的Item不是同一個時,將Slot與PickedItem交換
      ChangeSlotAndPickedItem(clickedItemUI, invetorySystem);
     }
    }
   } else {
    if (invetorySystem.isPickedItem == true) {
     if (Input.GetKey (KeyCode.Z)) {
      // 壓住Z鍵時,只會一個一個的放入物體
      PutInOneItem (invetorySystem);
     } else {
      // 放入所有物體
      PutInAllItemToEmptySlot (invetorySystem);
     }
    }
   }
  }

  private void PickupHalfOfSlotItem(ItemUI clickedItemUI, InventorySystem invetorySystem){
   int amountToPick = (clickedItemUI.Amount + 1) / 2;
   int amountRemained = clickedItemUI.Amount - amountToPick;
   // 將PickedItem的ItemUI設置為目前滑鼠點中的ItemUI
   invetorySystem.PickupItem (clickedItemUI.Item, amountToPick);
   if (amountRemained == 0) {
    Destroy (clickedItemUI.gameObject);
   } else {
    clickedItemUI.SetAmount (amountRemained);
   }
  }

  private void PickupAllSlotItem(ItemUI clickedItemUI, InventorySystem invetorySystem){
   // 將PickedItem的ItemUI設置為目前滑鼠點中的ItemUI
   invetorySystem.PickupItem (clickedItemUI.Item, clickedItemUI.Amount);
   // 因為已經選中物體了,要將Slot內的ItemUI刪除
   Destroy (clickedItemUI.gameObject);
  }

  private void PutInOneItem(InventorySystem invetorySystem){
   this.StoreItem (invetorySystem.GetPickedItem ().Item);
   invetorySystem.ReducePickedItem (1);
  }

  private void PutInAllItem(ItemUI clickedItemUI, InventorySystem invetorySystem){
   if (clickedItemUI.Item.Capacity > clickedItemUI.Amount) {
    int slotRemained = clickedItemUI.Item.Capacity - clickedItemUI.Amount;

    if (slotRemained >= invetorySystem.GetPickedItem ().Amount) {
     // 若Slot內的空間足以放下滑鼠拿著的全部物體
     clickedItemUI.SetAmount (clickedItemUI.Amount + invetorySystem.GetPickedItem ().Amount);
     invetorySystem.ReducePickedItem (invetorySystem.GetPickedItem ().Amount);
    } else {
     // Slot內的空間不夠放,只能放下部分滑鼠拿著的物體
     clickedItemUI.SetAmount (clickedItemUI.Amount + slotRemained);
     invetorySystem.ReducePickedItem (slotRemained);
    }
   }
  }

  private void PutInAllItemToEmptySlot(InventorySystem invetorySystem){
   for (int i = 0; i < invetorySystem.GetPickedItem ().Amount; i++) {
    this.StoreItem (invetorySystem.GetPickedItem().Item);
   }
   invetorySystem.ReducePickedItem (invetorySystem.GetPickedItem().Amount);
  }

  private void ChangeSlotAndPickedItem(ItemUI clickedItemUI, InventorySystem invetorySystem){
   Item item = clickedItemUI.Item;
   int amount = clickedItemUI.Amount;
   clickedItemUI.SetItem(invetorySystem.GetPickedItem().Item, invetorySystem.GetPickedItem().Amount);
   invetorySystem.GetPickedItem ().SetItem (item, amount);
  }
 }
}

執行遊戲看看吧,我用滑鼠點擊了背包內的能量瓶。

然後朝著血量瓶的位置點擊下去,能量瓶就會放下去,然後滑鼠上抓著血量瓶。

留言