Related Posts Plugin for WordPress, Blogger...

5-18 Forge Panel

本章要來教大家製作鍛造系統,首先,我們要有一個儲存鍛造公式的Json文件,分別紀錄材料ID跟數量,以及鍛造結果的ID,範例如下:

{"ForgeFormulaEntityList":[
    {
        "Item1ID":15,
        "Item1Amount":1,
        "Item2ID":17,
        "Item2Amount":2,
        "ResultID":13
    },
    {
        "Item1ID":16,
        "Item1Amount":1,
        "Item2ID":17,
        "Item2Amount":5,
        "ResultID":8
    }
]}

記得要將Json文件儲存在Resources資料夾底下,我們會用Resource.Load讀取。

接著,因為目前的介面已經滿了,所以我將商店的介面藏起來,如下圖。

隱藏的方式,請在Vendor Panel的Inspector內設定Panel的Panel Alpha為0,Canvas Group的Alpha為0即可,如下圖。

接著我們複製一下Vendor Panel重複利用。

取名為Forge Panel,我們的鍛造介面只需要兩個Slot,所以將其他無用的Slot刪除。

我複製了Close Btn,改成啟動鍛造的按鈕,取名為Forge Btn。

在Forge Btn底下新增UI/Text,文字寫『開始』,請大家自己設定一下字型大小跟顏色。

完成後,介面大致上如下圖。

接著建立Script名為ForgeFormula.cs,這是要用來放置解析Json資料後的類別。
 ForgeFormula.cs:

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

namespace RPG.Inventory{
 [Serializable]
 public class ForgeFormulaList{
  public List ForgeFormulaEntityList;
 }

 [Serializable]
 public class ForgeFormula : ISerializationCallbackReceiver{
  public int Item1ID;
  public int Item1Amount;
  public int Item2ID;
  public int Item2Amount;
  public int ResultID;

  List requireList = new List ();

  public void OnAfterDeserialize(){
   for (int i = 0; i < Item1Amount; i++) {
    requireList.Add (Item1ID);
   }
   for (int i = 0; i < Item2Amount; i++) {
    requireList.Add (Item2ID);
   }
  }

  public List GetRequireList(){
   return requireList;
  }

  public void OnBeforeSerialize(){}

  public bool MatchFormula(List materialsIDList){
   List checkList = new List (materialsIDList);
   foreach(int ID in requireList){
    bool isSuccess = checkList.Remove (ID);
    if (isSuccess == false) {
     return false;
    }
   }
   return true;
  }
 }
}

再來建立ForgeSlot.cs,由於本次鍛造的Slot沒有特殊處理,所以沒有覆寫OnPointerDown方法,另外也可用來讓InventorySystem識別不同種類的Slot。
ForgeSlot.cs

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

namespace RPG.Inventory{
 public class ForgeSlot : Slot {

 }
}

記得Forge Panel內的兩個ForgeSlot也要修改為ForgeSlot.cs唷!


再來是修改InventorySystem的部分啦!我將主要修改的程式碼截圖說明,如下圖為解析Json的部分。

再來是於Forge Panel按下『開始』按鈕的事件,我們先要取得目前Forge Slot內有哪些Item,並將這些材料存入materialsIDList。

然後用foreach迴圈取得每個ForgeFormula物件,並與materialIDList一一配對材料是否符合,符合公式的話便將鍛造成果存到背包內,並消耗對應的材料。
以下提供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;
  [SerializeField] CharacterProperty characterProperty;

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

  int coinAmount = 100;
  Text coinText;

  void Start () {
   ParseItemsJson ();
   ParseForgeFormulaJson ();
   slotList = GetComponentsInChildren ();
   coinText = GameObject.Find ("Coin").GetComponentInChildren();
   coinText.text = coinAmount.ToString();
   InitialVendorItem ();
  }

  void Update(){
   PressGToAddItem ();
   MovePickedItemByMousePosition ();
   DiscardPickedItem ();
  }

  private bool ConsumeCoin(int amount){
   if (coinAmount > 0) {
    coinAmount -= amount;
    coinText.text = coinAmount.ToString ();
    return true;
   }
   return false;
  }

  private void EarnCoin(int amount){
   coinAmount += amount;
   coinText.text = coinAmount.ToString ();
  }

  public void BuyItem(Item item){
   bool isSuccess = ConsumeCoin (item.BuyPrice);
   if (isSuccess) {
    StoreItem (item);
   }
  }

  public void SellItem(int sellAmount){
   int sellPrice = pickedItem.Item.SellPrice * sellAmount;
   ReducePickedItem (sellAmount);
   EarnCoin (sellPrice);
  }

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

  public ItemUI GetPickedItem(){
   return pickedItem;
  }

  public Slot[] GetSlotList(){
   return slotList;
  }

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

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

  public void UpdatePropertyText(){
   characterProperty.UpdatePropertyText ();
  }

  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) {
    // 避開VendorSlot
    if (slot is VendorSlot) {
     continue;
    }
    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) {
    // 避開VendorSlot
    if (slot is VendorSlot) {
     continue;
    }
    if (slot.transform.childCount == 0) {
     // 將Item存進該Slot
     slot.StoreItem (item);
     return true;
    }
   }
   Debug.LogError ("No Empty Slot.");
   return false;
  }

  public void ForgeItem(){
   List materialsIDList = new List ();
   List forgeSlotList = new List ();
   // 取得Forge Slot內的Item
   foreach (Slot slot in slotList) {
    ForgeSlot forgeSlot = (slot as ForgeSlot);

    if (forgeSlot && slot.transform.childCount > 0) {
     ItemUI currentItemUI = slot.transform.GetComponentInChildren ();
     for (int i = 0; i < currentItemUI.Amount; i++) {
      materialsIDList.Add (currentItemUI.Item.ID);
      forgeSlotList.Add (forgeSlot);
     }
    } 
   }
   // 檢查item組合是否匹配任一鍛造公式
   foreach (ForgeFormula formula in forgeFormulaList.ForgeFormulaEntityList) {
    if (formula.MatchFormula (materialsIDList)) {
     // 符合公式,存入鍛造物品到背包內
     StoreItem (formula.ResultID);
     // 消耗材料
     ConsumeMaterials(formula, forgeSlotList);
     break;
    }
   }
  }

  private void ConsumeMaterials(ForgeFormula formula, List forgeSlotList){
   List requireList = formula.GetRequireList();
   foreach (int ID in requireList) {
    foreach (ForgeSlot forgeSlot in forgeSlotList) {
     if (forgeSlot.transform.childCount > 0 && forgeSlot.GetItemID() == ID) {
      ItemUI itemUI = forgeSlot.GetComponentInChildren ();
      itemUI.ReduceAmount (1);
      if (itemUI.Amount <= 0) {
       DestroyImmediate (itemUI.gameObject);
      }
      break;
     }
    }
   }
  }

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

  private void InitialVendorItem(){
   Item[] vendorItemList = new Item[10];
   // 手動初始化要放入商店的物品
   vendorItemList [0] = GetItemByID (1);
   vendorItemList [1] = GetItemByID (2);
   vendorItemList [2] = GetItemByID (3);
   vendorItemList [3] = GetItemByID (4);
   vendorItemList [4] = GetItemByID (5);
   vendorItemList [5] = GetItemByID (6);
   vendorItemList [6] = GetItemByID (7);
   vendorItemList [7] = GetItemByID (8);
   vendorItemList [8] = GetItemByID (1);
   vendorItemList [9] = GetItemByID (2);
   foreach (Item item in vendorItemList) {
    foreach (Slot slot in slotList) {
     // 限定Slot類型為VendorSlot
     VendorSlot vendorSlot = (slot as VendorSlot);
     // 如果同類型物品疊加在一起
     if (vendorSlot && slot.transform.childCount >= 1 
      && slot.GetItemID () == item.ID &&
      slot.IsFilled () == false) {
      slot.StoreItem (item);
      break;
     } // 如果沒有同類物品,則放入空Slot
     else if (vendorSlot && slot.transform.childCount == 0) {
      slot.StoreItem (item);
      break;
     }
    }
   }
  }

  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) {
    // TODO 跟Slot.cs的PickupAllSlotItem相衝突,若用DestroyImmediate會導致此方法被呼叫
    isPickedItem = false;
    pickedItem.Hide ();
   }
  }

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

  private void ParseForgeFormulaJson(){
   TextAsset json = Resources.Load ("ForgeFormula");
   forgeFormulaList = JsonUtility.FromJson (json.text);
  }
 }
}

最後,記得在Forge Panel的Forge Btn中的Button事件設定,要將InventorySystem拉進去,並設定方法為ForgeItem唷!我的範例中,因為InventorySystem是放在Canvas內,所以如下圖會看見我拉進去的是Canvas。

來執行遊戲看看吧,我放入一個鍛造秘笈跟兩個鐵塊。

按下『開始』後,背包內出現了一把斧子,然後材料也被消耗掉了。

留言