Related Posts Plugin for WordPress, Blogger...

5-15 Create Character Panel And Wear Equipment

本章要來製作角色面板以及實作裝備穿戴功能,在背包介面按下滑鼠右鍵可以自動穿戴裝備,且裝備會自動放置到對應裝備類型的Slot。如果角色已經穿上了某個類型的裝備,則會與背包內的裝備交換。

首先實作前先解決一個Bug,我發現裝備的類型沒有正常解析,如下圖。靴子的裝備類型出現頭部,很顯然解析出錯。

請大家將Item.cs的OnAfterDeserialize方法設為virtual方法。

然後將Equipment.cs的OnAfterDeserialize方法設為override方法,這樣才能正確複寫父類別的OnAfterDeserialize。


接著來創建角色面板吧。首先,我們必須先新增一個Script名為EquipmentSlot,這個類別要繼承原本的Slot.cs。

在新的EquipmentSlot.cs中,我們會重新實作OnPointerDown方法,如下圖。先確認手上是否有裝備,且手上的裝備類型必須要跟Slot指定的裝備類型相同,才可以放進Slot。如果Slot內已經有裝備了,則會跟手上的裝備進行交換。如果是手上沒有裝備的狀態,則能直接取得Slot內的裝備。

然後,在裝備Slot內按下右鍵,就會自動脫下裝備放到背包中。

以下提供EquipmentSlot.cs完整程式碼:

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

namespace RPG.Inventory{
 public class EquipmentSlot : Slot {
  [SerializeField] public EquipmentType equipmentType;

  public override void OnPointerDown(PointerEventData eventData){
   // 使用滑鼠右鍵自動脫下裝備
   if (eventData.button == PointerEventData.InputButton.Right
    && invetorySystem.isPickedItem == false && transform.childCount > 0) {

    ItemUI clickedItemUI = GetComponentInChildren ();
    invetorySystem.StoreItem (clickedItemUI.Item);
    Destroy (clickedItemUI.gameObject);
   }

   if (eventData.button != PointerEventData.InputButton.Left)
    return;

   if (invetorySystem.isPickedItem == true) {
    Item pickedItem = invetorySystem.GetPickedItem ().Item;
    //  判斷手上的裝備類型是否與該Slot的裝備類型相同
    if (EquipmentTypeIsEqual (pickedItem)) {
     if (transform.childCount > 0) {
      // 若裝備槽有東西,將手上的物品與裝備槽交換
      ItemUI clickedItemUI = transform.GetChild (0).GetComponent ();
      invetorySystem.GetPickedItem ().ExchangeItem (clickedItemUI);
     } else {
      // 若裝備槽沒東西,直接把手上的東西放進去
      PutInOneItem();
     }
    }
   } else {
    if (transform.childCount > 0) {
     // 若裝備槽有東西,將裝備拿到手上
     ItemUI clickedItemUI = transform.GetChild (0).GetComponent ();
     PickupAllSlotItem(clickedItemUI);
    }
   }
  }

  public bool EquipmentTypeIsEqual(Item item){
   return item is Equipment && (item as Equipment).TheEquipmentType == equipmentType;
  }
 }
}


接著,請大家複製Knapsack Panel後,製作一個新的Character Panel,如下圖。

排版跟畫面如下圖。

要特別注意的是,每個Slot的Script要改成EquipmentSlot。

修改完後,請大家記得針對不同的Slot,選擇不同的EquipmentType參數。

並將新的EquipmentSlot拉進Project視窗變成Prefab。

 接著,我們在原本的Slot.cs的OnPointerDown方法中,新增按下滑鼠右鍵就能自動穿戴裝備的功能,如下圖。


以下提供新的Slot.cs跟InventorySystem.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;

  protected InventorySystem invetorySystem;
  void Start(){
   invetorySystem = GetComponentInParent ();
  }

  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;
    invetorySystem.ShowToolTip (item.GetToolTipText ());
   }
  }

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

  public virtual void OnPointerDown(PointerEventData eventData){
   // 使用滑鼠右鍵自動穿戴裝備
   if (eventData.button == PointerEventData.InputButton.Right
    && invetorySystem.isPickedItem == false && transform.childCount > 0) {

    ItemUI clickedItemUI = GetComponentInChildren ();
    invetorySystem.WearingEquipment (clickedItemUI);
   }

   if (eventData.button != PointerEventData.InputButton.Left)
    return;

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

  private void PickupHalfOfSlotItem(ItemUI clickedItemUI){
   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);
   }
  }

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

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

  private void PutInAllItem(ItemUI clickedItemUI){
   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(){
   for (int i = 0; i < invetorySystem.GetPickedItem ().Amount; i++) {
    this.StoreItem (invetorySystem.GetPickedItem().Item);
   }
   invetorySystem.ReducePickedItem (invetorySystem.GetPickedItem().Amount);
  }
 }
}

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(){
   PressGToAddItem ();
   MovePickedItemByMousePosition ();
   DiscardPickedItem ();
  }

  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 void WearingEquipment(ItemUI equipmentToWear){
   if ((equipmentToWear.Item is Equipment) == false) {
    return;
   }

   foreach(Slot slot in slotList){
    EquipmentSlot equipmentSlot = (slot as EquipmentSlot);
    if(equipmentSlot && equipmentSlot.EquipmentTypeIsEqual (equipmentToWear.Item)){
     if (equipmentSlot.transform.childCount > 0) {
      // 人物已配戴裝備了,將裝備與背包的裝備交換
      equipmentSlot.GetComponentInChildren ().ExchangeItem(equipmentToWear);
     } else {
      // 人物未配戴裝備,直接穿上
      equipmentSlot.StoreItem (equipmentToWear.Item);
      // 銷毀背包內的裝備
      Destroy (equipmentToWear.gameObject);
     }
     break;
    }
   }
  }

  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 void PressGToAddItem(){
   // 測試程式碼,手動生成物品
   if (Input.GetKeyDown (KeyCode.G)) {
    StoreItem (Random.Range(1,17));
   }
  }

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

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

  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);
  }
 }
}

最後,來執行遊戲看看吧,可以穿戴裝備了。





留言