【Unity】Fungusを用いたアイテムシステムの構築

## はじめに
Unityでゲームを作っていてある程度のレベルになると、アイテムの実装という壁に当たることが多いかと思います。アイテムを取得すると何らかの処理を行うというのはできても、取得したアイテムをアイテムボックスに保持して、そこから任意のタイミングで使用するというのは、なかなかハードルが高いかと思います。今回はいくつかのスクリプトで関数を相互参照させて、アイテムボックス機能を実装していきたいと思います。今回はスマートにというよりもわかりやすくという点に重点を置きましたのでところどころ冗長な表現になってることをご了承ください。

## 環境
- UnityEditor 2021.3.7f1

## 目標
目的を細分化させていきます。
- アイテム取得する
    - 取得したアイテムをステージ上から非表示にする
    - アイテムボックスに取得したアイテムの情報を受け渡す
- アイテムボックス一覧から取得済みのアイテムを確認する
    - アイテムボックスに取得したアイテムの画像を表示させる
- アイテムボックス一覧から取得済みのアイテムを使用する
    - 使用できる条件かを判定する
    - アイテムボックス一覧から使用したアイテムを削除する
    - アイテムボックスの状況を更新する

## 方針
主に3つのスクリプトとFungusのコマンドを組み合わせて実装していきます。
![スクリーンショット_20221213_115148.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2024218/4a655fec-12ad-65e5-e392-e2382df3c402.png)



## 前提知識
- Fungusについて
    - 導入：[Unityのアセットストアから消えた「Fungus」をインストールする方法](https://trend-tracer.com/fungas-install/)
- 関数と引数について
    - 参考：[【Unity入門】関数を使ってみる](https://mogi0506.com/unity-function/#:~:text=%E9%96%A2%E6%95%B0%E3%81%AB%E5%80%A4%E3%82%92%E5%85%A5%E3%82%8C%E3%82%8B,%E5%80%A4%E3%82%92%E5%87%BA%E5%8A%9B%E3%81%97%E3%81%BE%E3%81%99%E3%80%82&text=%E3%81%A8%E8%A8%98%E8%BF%B0%E3%81%97%E3%81%BE%E3%81%99%E3%80%82,%E3%81%99%E3%82%8B%E5%80%A4%E3%81%AE%E3%81%93%E3%81%A8%E3%81%A7%E3%81%99%E3%80%82)
- C# 他のスクリプトの変数を参照する
    - 参考：[Unity 別スクリプトの変数を変更する](https://justaday.hatenablog.com/entry/2022/03/28/225347)
- C# 他のスクリプトの関数を呼び出す
    - 参考：[他のスクリプトの関数を実行する](https://futabazemi.net/notes/script_function)


## ソースコード
### 1.スクリプト
```C#:Item.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using UnityEngine.UI;
using Fungus;

public class Item : MonoBehaviour
{
    public static Item instance;
    [SerializeField] Flowchart Log1;
    private Sprite image;
    private int index;
    public enum ItemType
    {
        knife,
        wateringCan,
        SuitKey,
        BreakerKey,
        illustration1,
        illustration2,
        illustration3,
        scissors,
        code,
        memo
    }

    public static Dictionary<ItemType, bool> itemCanUse = new Dictionary<ItemType, bool> ();
    public ItemType item;

    private void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }
    }

    private void Start()
    {
        index = (int)item;
        btn = GetComponent<Button>();
        myTransform = this.transform;
        foreach(ItemType x in System.Enum.GetValues(typeof(ItemType)))
        {
            if(!itemCanUse.ContainsKey(x))
            {
                itemCanUse.Add(x, false);   
            }
        }
    
    }

    public void OnThis()
    {
        gameObject.SetActive(false);
        ItemBox.instance.SetItem(index);
    }

    public void UseItem()
    {
        Debug.Log(itemCanUse.Values.ToArray()[index]);
        if(itemCanUse.Values.ToArray()[index]){
            string indexstr = index.ToString();
            Log1.SendFungusMessage("useItem_" + indexstr);
            ItemBox.instance.UsedItem(index);
        }
        else{
            Log1.SendFungusMessage("canNotUse");
        }
    }

}
```
```C#:ItemBox.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Fungus;


public class ItemBox : MonoBehaviour
{
    public GameObject[] boxs;
    public int[] ItemInBox;
    public static ItemBox instance;
    public GameObject ItemBoxUI;
    public GameObject ItemSelected;
    [SerializeField] Flowchart Log1;

    private int BoxNumber = 0;
    private void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }
    }

    private void Start()
    {
        for(int x=1; x<=7; ++x)
        {
            boxs[x].SetActive(false);
        }
    }

    public void ReloadBox()
    {
        int c = 0;
        bool emptyBox = false; 
        for(int a=0; a<=7; ++a)
        {
            if (emptyBox == true &&  boxs[a].activeSelf == true)
            {
                boxs[a-1].SetActive(true);
                boxs[a-1].gameObject.GetComponent<Image>().sprite = boxs[a].gameObject.GetComponent<Image>().sprite;
                ItemInBox[a-1] = ItemInBox[a];
                boxs[a].SetActive(false);
            }
            emptyBox = true; 
            if (boxs[a].activeSelf == true)
            {
                c = c + 1;
                emptyBox = false; 
            }
            
        }

        BoxNumber = c + 1;
    }

    public void SetItem(int index)
    {
        string indexstr = index.ToString();
        boxs[BoxNumber].gameObject.GetComponent<Image>().sprite = Resources.Load<Sprite>(indexstr);
        boxs[BoxNumber].SetActive(true);
        ItemInBox[BoxNumber] = index;
        ItemBoxUI.SetActive(true);
        SelectItem(index);
        BoxNumber = BoxNumber + 1;
    }

    public void SelectItem(int index)
    {
        string indexstr = index.ToString();
        ItemSelected.gameObject.GetComponent<Image>().sprite = Resources.Load<Sprite>(indexstr);
        Log1.SendFungusMessage("Item_" + indexstr);
    }

    public void DeleteItem(int ItemNumber)
    {
        for(int d=0; d<=7; ++d)
        {
            if (ItemInBox[d] == ItemNumber)
            {
                boxs[d].SetActive(false);
            }
        }
    }

    public void OnItem(int itemButtonNumber) { SelectItem(ItemInBox[itemButtonNumber]);  }

    public void UsedItem(int itemNumber) {
    int deletePositonNum = 0;
    for(int a=0; a<=7; ++a)
        {
            if(ItemInBox[a] == itemNumber)
            {
                deletePositonNum = a;
            }
        }
    DeleteItem(deletePositonNum); 
    ItemSelected.SetActive(false);
    }
}
    
```
```C#:UIManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UIManager : MonoBehaviour
{
    public GameObject ItemBoxUI;

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    public void OpenCloseItemBox()
    {
        if(ItemBoxUI.activeSelf == true)
        {
            ItemBoxUI.SetActive(false);
        }
        else
        {
            ItemBoxUI.SetActive(true);
            ItemBox.instance.ReloadBox();
        }
    }

}
```
### 2.Fungusの設定
Fungusの導入や基本設定は終わっている前提で進めていきます。
Scene内にFlowchartとSayDialog、MenuDialogを設置しておいてください。
FlowchartWindowでブロックとコマンドの設定をしていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2024218/0c092619-5863-b9ec-5524-e17ca3e1aae9.png)
今回はとりあえず二つだけアイテムを実装していきます。
##### 各アイテムのブロック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2024218/c042f986-a3ac-0fe5-b8af-8cb1e9caba78.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2024218/cdf85d6e-ece4-d4a3-c31b-f766e320781b.png)
Itemスクリプトのenumのindexに対応する形でItem_0、Item_1、、、、とMessaageReceivedのMessaageを設定し、ItemBoxスクリプトのSelectItem()から発火できるように設定します。
Sayコマンドを設定し、アイテム選択時に流れるメッセージを設定します。
また、選択したアイテムをItemSelectedに表示できるようにSetActiveを設定します。


##### Backのブロック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2024218/e7f16f53-ecce-1915-cfa9-56f01dbbf82a.png)
上でtrueにしたItemSelectedのActive状態をfalseにします。

##### useのブロック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2024218/d0dcf468-5ae3-6735-7948-af279e08d06e.png)
InvokeMethotコマンドで対応するアイテムのobjectを相手取り、ItemスクリプトのUseItem()を呼び出します。

##### can_not_useのブロック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2024218/035280f9-28e0-662d-e9e9-18cce6962b3a.png)
Item使用不可のメッセージとItemSelectedのActive状態をfalseにするコマンドを設定します。

##### 各use_itemのブロック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2024218/68309677-ecc0-39e9-53e1-6432568e8d27.png)
ItemスクリプトのUseItem()からメッセージ”use_item_「各index」”を受けっとたら発火するように設定します。
コマンドには実際にアイテムを使用した時の処理を設定してください。今回はSayだけしか設定していませんが、スクリプトの関数を呼び出すと幅広い処理ができると思います。

### 3.エディタ上での設定
##### アイテムの設置
今回はUIのボタンをアイテムにしているので、ステージ上にCanvasを設置し、そのCanvas上にアイテムを設置しています。ItemScriptをアタッチし、Buttonには自身のOnThis()を設定します。必要に応じてImageコンポーネントもアタッチしてください。また、enumのItemtypeを宣言しているので、そのアイテムが何であるかをエディタから設定するのを忘れないでください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2024218/1b18219a-d229-a72a-9d74-64f566410c3c.png)


##### アイテムボックスの設置
アイテムボックスのUI（つまりアイテムボックスを開いた時の枠）を設置し、その子要素としてItemSelected、Itemsそしてその子要素にItem0~Item7を設置します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2024218/5c5a717a-7b4d-e6b3-fa45-616595d5a036.png)
ItemsはItem0~Item7をまとめるためのフォルダのようなものなので、何もアタッチする必要はありません。
ItemSelectedはアイテムボックスからアイテムが選択された時のピックアップ表示を担うものです。ItemBoxとItemSelectedはImage、Item0~Item7にはImageとButtonがついていれば大丈夫です。

##### ItemManagerの設置
CreateEmptyでからのObjectを作り、ItemBoxスクリプトをアタッチしてください。publicで宣言したあれこれも設定していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2024218/1c20789b-316b-e108-1f9e-b43a0fc22caf.png)

##### Item0~Item7の設定
Item0~Item7のbuttonに関数をセットしていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2024218/c4bed656-c8a7-9c41-b17e-48be5791600c.png)
ItemManagerをセットし、ItemBoxのOnItemを呼び出します。OnItemには引数を設けているので、Item0であれば0を、Item1であれば1を、といった具合に入力していってください。

### 3.その他の設定
##### アイテムボタンの設定
CreateEmptyでUiManagerオブジェクトを用意し、UiManagerスクリプトをアタッチします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2024218/33fbf366-f3ed-6060-a810-dd8bf52c7b3d.png)
ItemBoxUIをセットしておきます。
Canvas上にアイテムボタンを作り、UiManagerのOpenCloseItemBoxをbuttonにセットすればアイテムボタンの用意は完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2024218/2a8e0854-5fd9-3902-acd9-454a896e0404.png)

##### アイテムの使用可否の設定
```C#:ItemManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ItemManager : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKey (KeyCode.DownArrow)) {
            Item.itemCanUse[Item.ItemType.knife] = true;
            Debug.Log("sssssss");
        }
    }
}
```
ItemスクリプトのDictionary型変数itemCanUseのkey「knife」を使ってvalueになっているboolを書き換えることで、同一シーン内であればどこからでもアイテムの使用可否を設定することができます。
`Item.itemCanUse[Item.ItemType.knife] = true;`